前言
表情包大家都用过,很多表情包上会有些辅助文字,来表达更多的内容含义。通过加文字特效,可能只用几个表情包,就能来场"表情包大战"。
有很多网站和工具可以操作gif,包括加文字、压缩、合成、制作表情包等等,但是大多需要登陆、看广告、甚至收费才能用,不如自己做一个。下面就介绍如何通过纯前端js来给gif表情包加文字。
思路是:先用第三方库把gif按帧分解为多个图片,然后再利用canvas插入图片再加文字,最后再利用第三方库把多个canvas合成一个gif文件。
用到的第三方库:
- omggif:github.com/deanm/omggi...
- libgif:github.com/buzzfeed/li...
- gif.js:github.com/jnordberg/g...
- gifshot:github.com/michael-ben...
这些第三方库,有的有npm package,有的没有,没有的可以直接在github上下载js文件,比如 omggif.js,然后可以script引用,也可以import url方式引用。
gif 转换成多个 canvas
利用第三方库把gif文件分解为多个图片或canvas。
方案1:omggif
omggif:github.com/deanm/omggi...
omggif
可以逐帧解析gif,没有文档,可以看官方例子。
逻辑:先读取gif buffer,然后通过omggif读取每个帧图片数据,然后画在canvas上,然后在每个canvas上添加文字,最后生成canvas list。
js
async function gif2canvas(url) {
var response = await fetch(url);
var blob = await response.blob();
var arrayBuffer = await blob.arrayBuffer();
var intArray = new Uint8Array(arrayBuffer);
var reader = new GifReader(intArray);
var info = reader.frameInfo(0);
return new Array(reader.numFrames()).fill(0).map((_, k) => {
var image = new ImageData(info.width, info.height);
reader.decodeAndBlitFrameRGBA(k, image.data);
var canvas = document.createElement('canvas');
canvas.width = info.width;
canvas.height = info.height;
var context = canvas.getContext('2d');
context.putImageData(image, 0, 0);
// draw text
context.font = `normal 30px sans-serif`;
context.fillStyle = 'white';
context.textAlign = 'center';
context.textBaseline = 'top';
context.fillText('不错不错!', info.width / 2, info.height - 50);
return canvas;
});
}
方案2:libgif
libgif:github.com/buzzfeed/li...
libgif
也可以逐帧解析gif,然后读取每帧图像,可以获取canvas,也可以支持播放、暂停等功能。但是不太稳定,经测试相比omggif
能解析成功的gif图片不多,大部分都会抛错。
逻辑:跟方案1类似,只是它传入的参数是image dom对象。
js
function gif2canvas2(image) {
return new Promise(resolve => {
image.setAttribute('rel:animated_src', image.src);
image.setAttribute('rel:auto_play', '0');
var rub = new SuperGif({ gif: image, max_width: image.width });
rub.load(function () {
var list = [];
for (var i = 1; i <= rub.get_length(); i++) {
rub.move_to(i);
var canvas = rub.get_canvas();
var newCanvas = document.createElement('canvas');
newCanvas.width = image.width;
newCanvas.height = image.height;
var context = newCanvas.getContext('2d');
context.drawImage(canvas, 0, 0);
// draw text
context.font = `normal 30px sans-serif`;
context.fillStyle = 'white';
context.textAlign = 'center';
context.textBaseline = 'top';
context.fillText('不错不错!', info.width / 2, info.height - 50);
list.push(newCanvas);
}
resolve(list);
});
});
}
多个 canvas 合成 gif 文件
利用第三方库把多个图片或者canvas,合成一个gif文件。
方案1:gif.js
gif.js:github.com/jnordberg/g...
gif.js
可以把多个frame合并成一个gif,且利用web workers提高解析速度。注意还需要把gif.worker.js
文件放在站点根路径下,或者用workerScript
来自定义路径。
js
function canvas2gif(canvasList, { width, height }) {
return new Promise(resolve => {
var gif = new GIF({
workers: 2,
quality: 10,
width: width,
height: height,
workerScript: '/lib/gif.worker.js',
});
canvasList.forEach(canvas => gif.addFrame(canvas, { delay: 100 }));
gif.on('finished', function (blob) {
var url = URL.createObjectURL(blob);
resolve(url);
});
gif.render();
});
}
方案2:gifshot
gifshot:github.com/michael-ben...
gifshot
跟gif.js
类似,它还可以合成视频或者多个gif,而且本身还支持添加文字。
用gifshot只支持图片格式转gif,所以需要canvas先转image,再调用api。
js
function canvas2gif2(canvasList, { width, height }) {
return new Promise(resolve => {
var loadImages = canvasList.map(canvas => {
var src = canvas.toDataURL('image/jpeg');
return loadImage(src);
});
Promise.all(loadImages).then(images => {
gifshot.createGIF({
images,
width: width,
height: height,
gifWidth: width,
gifHeight: height,
}, result => {
if (!result.error) {
resolve(result.image);
}
});
});
});
}
async function loadImage(src) {
return new Promise((resolve, reject) => {
var img = document.createElement('img');
img.src = src;
img.onload = function () {
resolve(img);
};
img.onerror = function (e) {
reject(e);
};
});
}
整合
html
<button id="btn1">click1</button>
<img id="img1" src="/images/gif1.gif">
<div id="canvas-div"></div>
<img id="img-output1">
<img id="img-output2">
js
var btn1 = document.getElementById('btn1');
var img1 = document.getElementById('img1');
var imgOutput1 = document.getElementById('img-output1');
var imgOutput2 = document.getElementById('img-output2');
var size = { width: img1.width, height: img1.height };
btn1.onclick = async () => {
// solution1: omggif
var canvasList = await gif2canvas(img1.src);
// solution2: libgif
// var canvasList = await gif2canvas2(img1);
// solution1: gif.js
var url1 = await canvas2gif(canvasList, size);
imgOutput1.src = url1;
// solution2: gifshot
var url2 = await canvas2gif2(canvasList, size);
imgOutput2.src = url2;
}
效果图:
gif.js方案输出的gif:
结论
本文介绍了如何纯前端给gif加文字,思路就是先转多个图片,然后再用canvas加文字,最后合成gif。
用到的第三方lib:
- omggif:github.com/deanm/omggi...
- 分解gif,经测试有些gif文件会解析失败,可能还不太稳定,如果有更好的库欢迎分享。
- libgif:github.com/buzzfeed/li...
- 也是分解gif,相比omggif更不稳定,能解析成功的很少。
- gif.js:github.com/jnordberg/g...
- 合成gif,Star多,相对比较稳定。
- gifshot:github.com/michael-ben...
- 合成gif,还支持播放暂停,加文字等,功能强大。
本文只是介绍了第三方相关库的使用,如果要做成Tools就能做成更多功能。比如在线播放gif,或者分解图片后展示在页面上,然后支持根据帧数来添加文字或特效,也就是支持gif里根据时间来显示或隐藏文字。
最后,毕竟客户端无论能力,还是性能、兼容性都有限,比如上面的库都只支持Web,不支持其它客户端例如小程序。尤其这种文件编码能力,其实在服务端能力更好,能用的库或者服务端的工具都很多,所以这种需求尽量还是放在后端实现。之后文章里也会介绍一些好用的nodejs第三方库,来实现更多图片处理需求。