如何使用 JavaScript 将多张 GIF 图合成为一张 GIF 图
点击上方 程序员成长指北 ,关注公众号
回复 1 ,加入高级Node交流群
-
解析 DOM 中包含的 GIF
-
重新生成 GIF 的每一帧
解析 GIF 插件 gifuct-js
解析出来每一帧的数据结构
{ // The color table lookup index for each pixel
pixels: [...], // the dimensions of the gif frame (see disposal method)
dims: { top: 0, left: 10, width: 100, height: 50
}, // the time in milliseconds that this frame should be shown
delay: 50, // the disposal method (see below)
disposalType: 1, // an array of colors that the pixel data points to
colorTable: [...], // An optional color index that represents transparency (see below)
transparentIndex: 33, // Uint8ClampedArray color converted patch information for drawing
patch: [...]
}
patch 表示当前帧图,数据格式是 Uint8ClampedArray
,可以使用 putImageData
在 canvas 渲染。
Automatic Patch Generation:If the buildPatch param of the dcompressFrames() function is true, the parser will not only return the parsed and decompressed gif frames, but will also create canvas ready Uint8ClampedArray arrays of each gif frame image, so that they can easily be drawn using ctx.putImageData() for example. This requirement is common, however it was made optional because it makes assumptions about transparency. The demo makes use of this option.
ctx.putImageData( new ImageData(patch, dims.width, dims.height),
dims.left,
dims.top);
生成 GIF 插件 gif.js
const gif = new GIF({ workers: 2, quality: 10 });
gif.addFrame(imageElement, { delay: 50 });
gif.on('finished', (blob) => { window.open(URL.createObjectURL(blob));
});
gif.render();
此插件没有 npm 包,只能将文件拷贝到项目中,通过 script 标签引用。
问题:多个 GIF 素材时长不一致,如何保证同时结束?
场景一
-
GIFa 时长 1000ms
-
GIFb 时长 1200ms
假设生成的 GIF 时长 1200ms,GIFb 正常结束,GIFa 丢失 200ms。
解决方案:取平均值
平均值:(1000 + 1200) / 2 = 1100
ms
GIFa 增加 100ms,假设总共 20 帧,单帧时长 50ms。则每帧增加 100 / 20 = 5
ms,为 55ms。
GIFb 减少 100ms,假设总共 24 帧,单帧时长 50ms。则每帧减少 100 / 24 = 4
ms,为 46ms。
场景二
-
GIFa 时长 1200ms
-
GIFb 时长 3200ms
生成的 GIF 时长 3200ms,GIFb 正常结束,GIFa 轮播 2 次,第 3 次播放 800ms,丢失 400ms。
解决方案:
-
计算最大公倍数
时长会很大,导致数据量很大。❌ -
计算最小时长与最大时长的合理倍数关系,最小时长 * 倍数。
function computeTotalTime(min: number, max: number) { const div = max / min; const decimal = div % 1; const multiple = decimal > 0.5 ? Math.ceil(div) : Math.floor(div); return min * multiple;
}
合理倍数关系,GIFa 与 GIFb 的合理倍数应该是 2 还是 3?
如果是 2,则二者差距是 3200 - (1200 * 2) = 800
ms;
如果是 3,则二者差距是 (1200 * 3) - 3200 = 400
ms;
差距越小越好,所以合理倍数关系为 3,总时长为 3600ms。
调整每张 GIF 图每帧的时长,适配总时长。
// 调整多张 gif 图的数据,适配总时长function adaptGifFrames(gifsData: GifData[], time: number) { // 适配时长
gifsData.forEach((gifData) => { const { totalTime, frames } = gifData; // 目标时长与当前 gif 图时长的倍数关系
const multiple = Math.floor(time / totalTime); // 目标时长与当前 gif 图时长的差值
let diff = time - totalTime; if (multiple !== 0) {
diff = (time - multiple * totalTime) / multiple;
} if (diff === 0) return;
frames.forEach((frame) => { // 依据每一帧占比增加时长
const precent = frame.delay / totalTime;
frame.delay += diff * precent;
}); // 设置适配后的总时长
gifData.totalTime = Math.round(
frames.reduce((pre, cur) => pre + cur.delay, 0)
);
});
}
问题:生成 GIF 图时,总共多少帧?每帧的延时时长是多少?
理想情况下,每帧延时时长为 50ms,但所使用的 GIF 图来源不一,无法保证其一致性。另外总时长越长,帧数就越多,体积就越大,GIF 图生成时间就越长。
所以限制帧数最大值为 100,如果超过 100,则每帧增加相应时长。
function computeGifData(gifsData: GifData[]) { const times = gifsData.map((gif) => gif.totalTime); const minTime = Math.min(...times); const maxTime = Math.max(...times); const div = maxTime / minTime; let totalTime = 0; if (div > 2) { // 计算最大时长
totalTime = computeTotalTime(minTime, maxTime);
} else { // 取平均时长
totalTime = times.reduce((pre, cur) => pre + cur, 0) / times.length;
} let delay = DEFAULT_GIF_DELAY; let frameLen = totalTime / delay; // 帧数
if (frameLen > MAX_GIF_FRAME) {
delay = delay * (frameLen / MAX_GIF_FRAME);
frameLen = MAX_GIF_FRAME;
} return { totalTime, frameLen, delay };
}
根据帧数 frameLen 循环获取每一帧的图片,延时时长为 delay。
gif.addFrame(image, { delay });
问题:字体较多导致生成速度较慢
中文字体文件体积大多以 M 为单位,导致每帧图片的 svg 体积较大,继而导致每帧图片加载耗时较长。
上图 3000ms ~ 10000ms,近 7s 时间都是在加载每帧的图片。
百度前端团队有个 NodeJS 工具 fontmin,可以按照指定文字内容对字体文件进行裁剪,返回仅包含指定文字内容的字体文件,可以极大的减少字体体积。
使用这个工具后,GIF 图生成速度得到显著提升。
全文完,如果觉得这篇文章对你有用,欢迎 点赞 👍、评论 ✍️、收藏 👀
喜欢点赞,再看,转发谢谢!
我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。
“分享、点赞 、 在看” 支持一波👍