灏天阁

写一个自己的GIF动图生成工具

· Yin灏

人生就像一场马拉松比赛,你永远不知道你要跑多久才能到达终点。常年写文章,文章里常常都会用到 GIF 动图来演示代码运行的效果图,越是容易获得的东西越不容易被重视,习惯于打开 GIF 应用来录制动图的我,突发奇想,能否自己开发一个在线的动图生成工具呢。说干就干,要干就干的漂亮。在本文中,我将向您展示如何使用 Canvas 和 gif.js 库创建自己的 GIF 动图生成工具。

一、工具拥有的能力

在本文中,我们将创建一个 GIF 动图生成工具,该工具可以让用户在 Canvas 上绘制动画,并将序列化的帧转换为 GIF 图像。该工具具有以下功能:

  • 可以选择多页面成画一次成画模式
  • 一次成画模式下能够记录用户在画布上绘制的所有笔画
  • 多页面成画 模式下可以将笔画添加到动画列表中,并生成 GIF 动画
  • 可以下载生成的 GIF 文件
  • 可以调整画笔大小和颜色
  • 可以上传图片作为动图元素

二、演示效果

1. 工具使用方法

  1. 进入工具会提示选择绘制动图模式有两种:’多页面成画’ : ‘一次成画
    😀一次成画:指一个画布上连续绘制,每一笔绘制的过程都会录制动图。
    😀多页面成画是:指每次绘制完一个页面需要切换下一个页面绘制下一帧,不会记录笔画。
  2. 页面左侧“动图列表展示每一帧动图”只有选择了多页面成画,才会展示。
  3. 多页面成画模式下,要将画布上绘制的内容添加到动图列表,然后点击生成,才会生成动图,并在页面下方展示。
  4. 一次成画模式下,直接在画布上绘制,画完后,直接点击生成 GIF 按钮。
  5. 其他用法可以通过使用过程中,自行摸索,例如画笔大小,上传图片,画笔颜色等。

2. 运行效果

体验链接:GIF 动画在线生成工具 (forrestyuan.github.io) 代码仓库:github.com/forrestyuan…

  • 多页面模式

demo.gif

  • 一次成画模式

demo2.gif

三、所用技术

在本文中,我们主要使用了以下技术来搭建我们的 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 的质量级别,widthheight属性指定了 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. 画布内容绘制

在画布上绘制内容,监听三个事件mousedownmousemovemouseup ,可谓是三剑客,在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 动图生成工具。然而,还有许多可以改进的地方。

  1. 可以写出更好看的 UI 页面。
  2. 添加橡皮擦工具,或者回退、前进的功能。
  3. 在多页面成画模式下,可以通过点击左侧 GIF 帧列表的某一帧,进行编辑。
  4. 可以调用摄像头拍照来放到 canvas 中进行帧创作
  5. 添加文字文案特效
  6. 表情包功能
  7. 动画特效,例如爆照效果等。

这里只是列了一些更有趣的功能,一千个读者一千个哈姆雷特,相信大家有更有趣的实现功能,出于时间有限,和知识为了带大家知道怎么去开发一个 GIF 生成工具,本人并没有花太多精力去开发完这些功能,有兴趣的小伙伴,到我的 github 上 fork 我的代码过去,修改,完善,弄出新鲜玩意来。

六、写在文末

本文主要介绍了如何使用 gif.js 和 lodash.js 库来开发一个基于 canvas 的 GIF 动图生成工具。文中详细地介绍了如何使用这些库来创建 GIF 实例、将帧添加到 GIF 动画中、生成 GIF 动画、生成 GIF 帧列表、上传图片作为动图元素等。同时,还介绍了一些有待提升的点,例如 UI 页面更美观、添加橡皮擦工具、在多页面成画模式下编辑 GIF 帧等。欢迎有兴趣的小伙伴到作者的 github 上 fork 代码并进行修改完善。

- Book Lists -