blob: 83378056722e44b81e5bf55f496021d62104632c [file] [log] [blame]
import canvasSnapshot from './canvasSnapshot.js'; // eslint-disable-line import/extensions
import wait from './wait.js'; // eslint-disable-line import/extensions
import puppeteerHelper from './puppeteerHelper.js'; // eslint-disable-line import/extensions
const calculateFrameAdvance = (renderSettings, anim) => {
const totalFrames = anim.op - anim.ip;
const sampleRate = renderSettings.sampleRate > 0 ? renderSettings.sampleRate : 1;
const traversedFrames = totalFrames / sampleRate;
let advance = 1 / sampleRate;
if (renderSettings.frames && renderSettings.frames < traversedFrames) {
advance = totalFrames / renderSettings.frames;
}
return advance;
};
const calculateGridSize = (renderSettings) => {
const grid = renderSettings.grid?.split('x');
if (grid && grid.length && grid.length === 2) {
const width = parseInt(grid[0]);
const height = parseInt(grid[1]);
if (typeof width === 'number' && isFinite(width) && typeof height === 'number' && isFinite(height)) {
return {
width,
height,
}
}
}
return null;
}
const calculateElementSize = (frameAdvance, anim, grid) => {
if (!grid) {
return {
width: anim.w,
height: anim.h,
}
} else {
const totalFrames = (anim.op - anim.ip) / frameAdvance;
const maxColumns = 30;
const cellCandidate = {
width: 0,
height: 0,
};
const elementRatio = anim.w / anim.h;
for (let i = 1; i <= maxColumns; i += 1) {
const cellWidth = grid.width / i;
const cellHeight = cellWidth / elementRatio;
const totalRows = Math.ceil(totalFrames / i);
if (totalRows * cellHeight < grid.height) {
if (cellWidth * cellHeight > cellCandidate.width * cellCandidate.height) {
cellCandidate.width = cellWidth;
cellCandidate.height = cellHeight;
cellCandidate.xPadding = (grid.width - cellWidth * i) / (i)
cellCandidate.yPadding = (grid.height - cellHeight * totalRows) / (totalRows)
}
}
}
return cellCandidate;
}
}
const loadSkottieModule = async () => {
const canvasKit = await window.CanvasKitInit({
locateFile: () => 'canvaskit.wasm',
});
return canvasKit;
};
const createCanvas = async (animationData) => {
const canvas = document.createElement('canvas');
canvas.width = animationData.w;
canvas.height = animationData.h;
canvas.setAttribute('id', 'skottie-canvas');
const lottie = document.getElementById('lottie');
lottie.appendChild(canvas);
return canvas;
};
const getAnimationData = async (rendererSettings) => {
const fetchResponse = await fetch(rendererSettings.path);
const animData = await fetchResponse.json();
return animData;
};
const createSkottiePlayer = async (canvasKit, animationData, canvas, assets) => {
// const { devicePixelRatio } = window; // TODO: check if using the pixel ratio is preferred.
const devicePixelRatio = 1;
canvas.width = devicePixelRatio * animationData.w; // eslint-disable-line no-param-reassign
canvas.height = devicePixelRatio * animationData.h; // eslint-disable-line no-param-reassign
const surface = canvasKit.MakeCanvasSurface(canvas);
const skcanvas = surface.getCanvas();
const animation = canvasKit.MakeManagedAnimation(
JSON.stringify(animationData), assets, '',
);
const goToAndStop = (pos) => {
animation.seekFrame(pos);
const bounds = canvasKit.LTRBRect(
0,
0,
animationData.w * devicePixelRatio,
animationData.h * devicePixelRatio,
);
skcanvas.clear(canvasKit.Color(0, 0, 0, 0.0));
animation.render(skcanvas, bounds);
surface.flush();
};
return {
goToAndStop,
};
};
const iterateFrames = async (player, animationData, canvas, renderSettings) => {
const snapshotsContainer = document.getElementById('snapshotsContainer');
const frameAdvance = calculateFrameAdvance(renderSettings, animationData);
const grid = calculateGridSize(renderSettings);
const elementSize = calculateElementSize(frameAdvance, animationData, grid);
snapshotsContainer.style.lineHeight = 0;
const width = elementSize.width;
const height = elementSize.height;
if (grid) {
snapshotsContainer.style.width = `${grid.width}px`;
// snapshotsContainer.style.height = `${grid.height}px`;
}
let currentFrame = 0;
const totalFrames = animationData.op - animationData.ip;
while (currentFrame < totalFrames) {
// Disabling rule because execution can't be parallelized
/* eslint-disable no-await-in-loop */
player.goToAndStop(currentFrame);
const element = canvasSnapshot(
canvas,
snapshotsContainer,
width,
height,
);
element.style.padding = `${elementSize.yPadding * 0.5}px ${elementSize.xPadding * 0.5}px`;
if (Number(renderSettings.individualAssets) === 0) {
await wait(1); // eslint-disable-line no-await-in-loop
} else {
await puppeteerHelper.submitAndWaitForResponse(
currentFrame,
currentFrame === totalFrames - 1,
width,
height,
);
element.remove();
}
currentFrame += frameAdvance;
/* eslint-enable no-await-in-loop */
}
};
const getAssets = async (animationData, rendererSettings) => {
const assets = {
};
await Promise.all(
animationData.assets
.filter((asset) => asset.p && asset.u)
.map(async (asset) => {
const assetPathParts = rendererSettings.path.split('/');
assetPathParts.pop();
const assetPath = `${assetPathParts.join('/')}/`;
const assetData = await fetch(assetPath + asset.u + asset.p);
assets[asset.p] = await assetData.arrayBuffer();
}),
);
return assets;
};
const start = async (rendererSettings) => {
try {
const canvasKit = await loadSkottieModule();
const animationData = await getAnimationData(rendererSettings);
const canvas = await createCanvas(animationData);
const assets = await getAssets(animationData, rendererSettings);
const skottiePlayer = await createSkottiePlayer(canvasKit, animationData, canvas, assets);
await iterateFrames(skottiePlayer, animationData, canvas, rendererSettings);
window._finished = true; // eslint-disable-line no-underscore-dangle
} catch (error) {
console.log('ERROR'); // eslint-disable-line no-console
console.log(error.message); // eslint-disable-line no-console
}
};
export default {
start,
};