Feat: Client-side GIF renderer

This commit is contained in:
2026-03-20 20:59:18 -07:00
parent a95181aba5
commit fa5dbfffea

48
assets/js/gif-builder.js Normal file
View File

@@ -0,0 +1,48 @@
const GIF_WORKER_URL = "https://cdn.jsdelivr.net/npm/gif.js@0.2.0/dist/gif.worker.js";
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error("Failed to load generated frame."));
img.src = src;
});
}
export async function createGifFromFrames(frameUrls, { fps = 4 } = {}) {
if (!Array.isArray(frameUrls) || !frameUrls.length) {
throw new Error("No frames available for GIF encoding.");
}
if (typeof window.GIF !== "function") {
throw new Error("GIF encoder is not loaded.");
}
const images = await Promise.all(frameUrls.map(loadImage));
const first = images[0];
const delay = Math.max(20, Math.round(1000 / Math.max(1, Number(fps) || 4)));
return new Promise((resolve, reject) => {
const gif = new window.GIF({
workers: 2,
quality: 10,
repeat: 0,
width: first.naturalWidth || first.width,
height: first.naturalHeight || first.height,
workerScript: GIF_WORKER_URL,
});
for (const img of images) {
gif.addFrame(img, { delay });
}
gif.on("finished", (blob) => resolve(blob));
gif.on("abort", () => reject(new Error("GIF rendering aborted.")));
try {
gif.render();
} catch (err) {
reject(err);
}
});
}