写一个自己的GIF动图生成工具
人生就像一场马拉松比赛,你永远不知道你要跑多久才能到达终点。常年写文章,文章里常常都会用到 GIF 动图来演示代码运行的效果图,越是容易获得的东西越不容易被重视,习惯于打开 GIF 应用来录制动图的我,突发奇想,能否自己开发一个在线的动图生成工具呢。说干就干,要干就干的漂亮。在本文中,我将向您展示如何使用 Canvas 和 gif.js 库创建自己的 GIF 动图生成工具。
一、工具拥有的能力
在本文中,我们将创建一个 GIF 动图生成工具,该工具可以让用户在 Canvas 上绘制动画,并将序列化的帧转换为 GIF 图像。该工具具有以下功能:
- 可以选择
多页面成画
或一次成画
模式 一次成画
模式下能够记录用户在画布上绘制的所有笔画多页面成画
模式下可以将笔画添加到动画列表中,并生成 GIF 动画- 可以下载生成的 GIF 文件
- 可以调整画笔大小和颜色
- 可以上传图片作为动图元素
二、演示效果
1. 工具使用方法
- 进入工具会提示选择绘制动图模式有两种:’
多页面成画
’ : ‘一次成画
’
😀一次成画:指一个画布上连续绘制,每一笔绘制的过程都会录制动图。
😀多页面成画是:指每次绘制完一个页面需要切换下一个页面绘制下一帧,不会记录笔画。 - 页面左侧“动图列表展示每一帧动图”只有选择了多页面成画,才会展示。
- 多页面成画模式下,要将画布上绘制的内容添加到动图列表,然后点击生成,才会生成动图,并在页面下方展示。
- 在
一次成画
模式下,直接在画布上绘制,画完后,直接点击生成 GIF 按钮。 - 其他用法可以通过使用过程中,自行摸索,例如画笔大小,上传图片,画笔颜色等。
2. 运行效果
体验链接:GIF 动画在线生成工具 (forrestyuan.github.io) 代码仓库:github.com/forrestyuan…
- 多页面模式
- 一次成画模式
三、所用技术
在本文中,我们主要使用了以下技术来搭建我们的 GIF 动图生成工具:
- gif.js: 一个基于 JavaScript 的库,可用于在 Web 应用程序中创建 GIF 动画。它允许您使用 Canvas API 绘制动画,并将序列化的帧转换为 GIF 图像。
- gif.worker.js:gif.js 库的一个 Web Worker,可在后台处理 GIF 编码,从而确保浏览器最佳性能。
- lodash.js: 一个 JavaScript 实用工具库,提供了许多实用的函数。它包含了很多有用的功能,例如数组操作、对象操作、函数操作、字符串操作等等。
四、核心代码实现
完整代码可以到我的 GitHub 仓库阅读哈!点我去代码仓库,阅读完整源码
1. 创建 gif.js 实例
为了使用 gif.js 库来创建 GIF 动画,我们需要创建一个gif.js
实例。我们可以使用以下代码创建一个实例:
const gif = new GIF({
workers: 2,
quality: 5,
debug: true,
width: canvas.width,
height: canvas.height,
});
这段代码使用gif.js
库创建了一个 GIF 实例对象。其中,workers
属性指定了两个 Web Worker,quality
属性指定了 GIF 的质量级别,width
和height
属性指定了 GIF 图像的宽度和高度。我们可以使用这个实例对象来添加帧并生成 GIF 动画。
2. 将帧添加到 GIF 动画中
要将帧添加到 GIF 动画中,我们需要将 Canvas 上的每一个像素数据复制一份保存到 GIF 中,gif.js
提供不止一种方法,我在开发的过程中,每种方法都去尝试了,尝试来尝试去,终究还是直接利用画布的像素数据拷贝最方便快捷,不过,这也跟需求有关,如果不是基于 canvas 来开发,那就另当别论了。
gif.addFrame(ctx, { copy: true, delay: 200 });
这段代码使用了gif.js
库中的addFrame()
方法将帧添加到了 GIF 动画中。其中,ctx
参数是 Canvas 上下文,delay
参数是帧之间的延迟时间(以毫秒为单位)。copy
参数指定是否将 Canvas 缓冲区复制到新的帧中。如果设置为false
,则只保存 Canvas 上下文的引用。这意味着,如果在添加帧时更改了 Canvas 上下文,则这些更改将反映在所有帧中。如果设置为true
,则会将 Canvas 缓冲区复制到新的帧中,从而确保每个帧都是独立的。
3. 生成 GIF 动画
当我们添加了所有的帧后,我们可以使用以下代码来生成 GIF 动画,首先,我们在try
块中调用gif.render()
方法来生成 GIF 动画。一旦生成完成,gif.on("finished")
方法将被调用。在这里,我们创建一个<img>
元素和一个下载链接<a>
元素,并将其添加到页面上。最后,我们调用reset()
方法来重置应用程序的状态。
// 生成GIF
function generateGif() {
try {
LoadingModal.style.display = "flex";
gif.on("finished", function (blob) {
LoadingModal.style.display = "none";
const img = document.createElement("img");
const link = document.createElement("a");
const url = URL.createObjectURL(blob);
img.src = url;
link.href = url;
link.textContent = "点击下载动图";
link.download = "myGif.gif";
link.appendChild(img);
gifResListBox.appendChild(link);
reset();
});
gif.render();
} catch (error) {
LoadingModal.style.display = "none";
}
}
4. 生成 GIF 帧列表
GIF 帧列表,只有在多页面成画
模式下才有作用,这个时候,一个画布内容为一帧,通过点击“添加到动图中”按钮调用gif.addFrame
方法保存帧。然后,使用 forEach()方法遍历gif.frames
中的每一帧。对于每一帧,我们创建一个与 GIF 图像相同大小的 ImageData 对象,并将其传递给 Canvas 上下文的putImageData()
方法。创建一个新的 Canvas 元素,并将其添加到绘制列表中。最后将ImageData
对象绘制到新的 Canvas 元素中。
// 将每一帧输出到Canvas元素中
drawListBox.innerHTML = "";
gif.frames.forEach((frame, index) => {
let w = gif.options.width;
let h = gif.options.height;
const imageData = new ImageData(frame.data, w, h);
const tempCavnas = document.createElement("canvas");
tempCavnas.width = w;
tempCavnas.height = h;
tempCavnas.style.cssText = "width:100px;height:100px";
const ctx = tempCavnas.getContext("2d");
ctx.putImageData(imageData, 0, 0);
drawListBox.appendChild(tempCavnas);
});
5. 画布内容绘制
在画布上绘制内容,监听三个事件mousedown
、mousemove
、mouseup
,可谓是三剑客,在mousedown
事件中调用beginPath()
方法来开始新的路径,并使用moveTo()
方法将路径的起点移动到鼠标的位置。将isDrawing
变量设置为true
,表示用户正在绘制
在mousemove
事件中检查当前是否正在绘制。如果不是,则返回。如果正在绘制,则我们设置画笔的线宽和颜色,并使用lineTo()
方法将路径移动到鼠标的当前位置,然后用stroke()
方法来绘制路径。
当绘制模式为once
,则将当前帧添加到动画列表中。最后,在mouseup
事件中将isDrawing
变量设置为false
,表示绘制已完成。
canvas.addEventListener("mousedown", (event) => {
ctx.beginPath();
ctx.moveTo(event.offsetX, event.offsetY);
isDrawing = true;
});
canvas.addEventListener("mousemove", (event) => {
if (!isDrawing) return;
ctx.lineWidth = brushSizeSelector.value;
ctx.strokeStyle = colorSelector.value;
ctx.lineTo(event.offsetX, event.offsetY);
ctx.stroke();
if (drawMode === "once") {
addFunc();
}
});
canvas.addEventListener("mouseup", () => {
isDrawing = false;
});
在多页面成画
模式下绘制新的帧,需要清空 Canvas 元素。
context.clearRect(0, 0, canvas.width, canvas.height);
6. 上传图片作为动图元素
为了上传图片,我们可以使用以下代码:
function handleImageUpload(event) {
const file = event.target.files[0];
const reader = new FileReader();
reader.onload = function (event) {
const img = new Image();
img.src = event.target.result;
img.onload = function () {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
};
};
reader.readAsDataURL(file);
}
在这里,我们使用FileReader
对象来读取上传的文件,并将其转换为 Base64 图像。然后,我们创建一个新的Image
对象,并在图像加载完成后将其绘制到 Canvas 元素上。
五、有待提升的点
至此,我们一起创建了一个简单的 GIF 动图生成工具。然而,还有许多可以改进的地方。
- 可以写出更好看的 UI 页面。
- 添加橡皮擦工具,或者回退、前进的功能。
- 在多页面成画模式下,可以通过点击左侧 GIF 帧列表的某一帧,进行编辑。
- 可以调用摄像头拍照来放到 canvas 中进行帧创作
- 添加文字文案特效
- 表情包功能
- 动画特效,例如爆照效果等。
这里只是列了一些更有趣的功能,一千个读者一千个哈姆雷特,相信大家有更有趣的实现功能,出于时间有限,和知识为了带大家知道怎么去开发一个 GIF 生成工具,本人并没有花太多精力去开发完这些功能,有兴趣的小伙伴,到我的 github 上 fork 我的代码过去,修改,完善,弄出新鲜玩意来。
六、写在文末
本文主要介绍了如何使用 gif.js 和 lodash.js 库来开发一个基于 canvas 的 GIF 动图生成工具。文中详细地介绍了如何使用这些库来创建 GIF 实例、将帧添加到 GIF 动画中、生成 GIF 动画、生成 GIF 帧列表、上传图片作为动图元素等。同时,还介绍了一些有待提升的点,例如 UI 页面更美观、添加橡皮擦工具、在多页面成画模式下编辑 GIF 帧等。欢迎有兴趣的小伙伴到作者的 github 上 fork 代码并进行修改完善。