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

人生就像一场马拉松比赛,你永远不知道你要跑多久才能到达终点。常年写文章,文章里常常都会用到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…

—–多页面模式—–
带你写一个自己的GIF动图生成工具

—-一次成画模式—-
带你写一个自己的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代码并进行修改完善。
本文正在参加「金石计划」

原文链接:https://juejin.cn/post/7221541217048625208 作者:forrest酱

(1)
上一篇 2023年4月14日 上午10:49
下一篇 2023年4月14日 上午10:59

相关推荐

发表评论

登录后才能评论