React 前端导航

如何使用 JavaScript 将多张 GIF 图合成为一张 GIF 图

利用 JavaScript 将多张 GIF 图合并为一张

基本思路

  • 解析 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 渲染。

ctx.putImageData(newImageData(patch, dims.width, dims.height),dims.left,dims.top);

生成 GIF 插件 gif.js

const gif = newGIF({ workers: 2, quality: 10 });
gif.addFrame(imageElement, { delay: 50 });
gif.on('finished', (blob) => {  
    window.open(URL.createObjectURL(blob));
});
gif.render();

gif.js 插件没有 npm 包,所以只能将相关文件拷贝到项目中,通过 script 标签引入来使用。

FAQ

1.多个 GIF 素材时长不一致,如何保证同时结束?

a.场景一

  • GIFa 时长 900ms
  • GIFb 时长 1300ms
    假设生成的 GIF 时长 1200ms,GIFb 正常结束,GIFa 丢失 100ms。

解决方案:取平均值
平均值:(900 + 1300) / 2 = 1100 ms

  • GIFa 增加 200ms,假设总共 20 帧,单帧时长 50ms。则每帧增加 200 / 20 = 19ms,为 60ms。
  • GIFb 减少 200ms,假设总共 24 帧,单帧时长 50ms。则每帧减少 200 / 24 = 8.33ms,为 41.67ms。

b.场景二

  • GIFa 时长 1200ms
  • GIFb 时长 3200ms
    生成的 GIF 时长 3200ms,GIFb 正常结束,GIFa 轮播 2 次,第 3 次播放 800ms,丢失 400ms。

解决方案:
1.计算最大公倍数
时长会很大,导致数据量很大。❌

2.计算最小时长与最大时长的合理倍数关系,最小时长 * 倍数。

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) = 800ms;
如果是 3,则二者差距是 (1200 * 3) - 3200 = 400ms;
差距越小越好,所以合理倍数关系为 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)
    );
  });
}
2.生成 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 });
3.字体较多导致生成速度较慢

中文字体文件体积大多以 M 为单位,导致每帧图片的 svg 体积较大,继而导致每帧图片加载耗时较长。

每帧图片加载耗时
上图 3000ms ~ 10000ms,近 7s 时间都是在加载每帧的图片。
百度前端团队有个 NodeJS 工具 fontmin,可以按照指定文字内容对字体文件进行裁剪,返回仅包含指定文字内容的字体文件,可以极大的减少字体体积。
使用这个工具后,GIF 图生成速度得到显著提升。

每帧图片加载耗时

声明:本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。邮箱:farmerlzj@163.com。 本站原创内容未经允许不得转载,或转载时需注明出处: 内容转载自: React前端网:https://qianduan.shop/blogs/detail/77
想做或者在做副业的朋友欢迎加微信交流:farmerlzj,公众号:生财空间站。

#js#GIF

相关推荐

原型与原型链、继承

原型与原型链、继承简单实现

浏览器中的js事件循环(Event loop)

本文将简述浏览器中的js事件循环机制,帮助我们理解浏览器环境js代码是如何运行的。Javascript的一大特点是单线程,也就意味着同一时间他只能做一件事。事件循环(Event Loop)是为了协调事件,用户交互,UI渲染,网络处理等行为,防止线程阻塞而诞生的。