使用Canvas在Worker中绘画gif图

923 阅读2分钟

前言

源码地址

该代码是基于 libgif 的基础上进行修改而成的。

简单说明 libgif 的实现

libgif 的实现主要是创建一个 canvasdom 元素,然后开启计时器,不断的往 canvas 中绘画处理好的每一帧的图片。

而我们根据该插件拿到的则是一个自带计时器,可以一直在绘画的 canvas

问题

一开始我不了解该插件,从网上搜来如何在 canvas 中绘画gif,而网上的各种使用方法,大多都是借助 libgif ,并且在后面会调用 play 方法,而这显然存在一定的内存泄漏的问题,因为这计时器是一直开着的,当然也可以通过它里面的 pause 方法来控制暂停,但是这样的使用方式对于我来说心智负担就有点重了,因为我原本只是需要获得一张gif的多帧图片而已。

而且在 worker 中不支持 dom 的方法,并且可以采用了 OffscreenCanvas 代替 canvas

最终实现

基于 libgif 的处理,判断了是否在 worker 下,将返回的数据进行一些处理,直接将每一帧数据都保存为一个全新的 canvas 最后得到一个可以直接用于绘画的数组。

最终实现的代码也参考了这篇文章

在html和js文件中简单使用

Kapture 2023-05-04 at 14.16.23.gif

html文件

<!DOCTYPE html>
<html lang='zh-cn'>
<head>
  <meta charset='UTF-8'>
  <meta http-equiv='X-UA-Compatible' content='IE=edge'>
  <meta name='viewport' content='width=device-width, initial-scale=1.0'>
  <title>Js</title>
</head>
<body>
  <canvas id="canvas" width="1000" height="450"></canvas>
  <script>
    var worker = null, canvasBitmap;
    function init() {
      canvasBitmap = document.getElementById('canvas');
      var offscreen = canvasBitmap.transferControlToOffscreen();
      worker = new Worker('drawgif.js');
      worker.postMessage({ msg: 'init', canvas: offscreen }, [offscreen]);
    }
    init()
  </script>
</body>
</html>

drawgif.js

loadGifToCanvas

  • 参数:

第一个参数传入 url ,第二个参数传入 是否处于 worker 环境下

  • 返回值:(具体可以看下方的ts类型介绍)

返回一个promise 的对象数组,该对象由一个 delay(图片应该持续的帧数) 和 img(可直接用于canvas绘画的图片资源) 组成。

worker-libgif.js的源码

// 将一个或多个脚本同步导入到worker的作用域中
importScripts('./worker-libgif.js') 

var offscreen,ctx;
onmessage = function (e) {
  var data = e.data;
  if(e.data.msg == 'init'){
    offscreen = data.canvas;
    ctx = offscreen.getContext("2d");
    draw();
  }else if(e.data.msg == 'draw'){
    draw();
  }
}

async function draw() {
  ctx.clearRect(0,0,offscreen.width,offscreen.height);

  const imgList = await loadGifToCanvas('../img/zombies_1_move.gif', true);
  const imgList2 = await loadGifToCanvas('../img/sun.gif', true);

  const drawimg1 = drawGif(imgList);
  const drawimg2 = drawGif(imgList2);
  (function go() {
    ctx.clearRect(0,0,offscreen.width,offscreen.height);
    drawimg1()
    drawimg2()
    requestAnimationFrame(go)
  })()
}

function drawGif(list) {
  let index = 0;
  const len = list.length * 4;
  return () => {
    ctx.drawImage(list[Math.floor(index / 4)].img, 100, 200);
    index++;
    if (index >= len) {
      index = 0;
    }
  }
}

在 vue 等项目中使用

安装依赖

npm i lhh-utils

ts类型

function loadGifToCanvas(url: string, isWorker: boolean): Promise<SourceImgObj[]>

type SourceImgObj = {
  delay: number
  img: OffscreenCanvas | CanvasImageSource
}

将gif图片转为可供canvas绘画的数组(支持worker)

// vue文件
<script setup lang='ts'>
import { onMounted, ref } from 'vue';
// 引入对应的 worker 文件
import Worker from "@/workers/test.ts?worker"

const workerRef = ref<Worker>()

const initWorker = () => {
  const canvasBitmap = document.getElementById('canvas') as HTMLCanvasElement;
  const offscreen = canvasBitmap.transferControlToOffscreen();
  workerRef.value = new Worker()
  // 该vue文件(主线程)往 worker (另外的线程) 传递数据
  workerRef.value.postMessage({ init: true, canvas: offscreen }, [offscreen]);
  // worker(另外的线程) 往 该vue文件(主线程)传递数据
  workerRef.value.onmessage = e => {
    console.log(e.data)
  }
}

onMounted(() => {
  initWorker()
})
</script>

<template>
  <div class='worker'>
    <canvas id="canvas" width="1000" height="450" :style="{border: '1px solid #fff'}"></canvas>
  </div>
</template>
// workers/test.ts (worker的文件)
import { loadGifToCanvas, SourceImgObj } from "lhh-utils";

const state = {
  ctx: null,
  offscreen: null,
}
addEventListener('message', e => {
  const { data } = e;
  // 这里可以接收到传递进来的消息
  console.log('data: ', data);
  state.offscreen = data.canvas;
  // 获取到canvas的上下文
  state.ctx = state.offscreen.getContext('2d');
  getGifImgList()
})
function getGifImgList() {
  const imgList = await loadGifToCanvas('../img/zombies_1_move.gif', true);
  console.log(imgList) 
  // 根据这个数组绘画就行了
}
export default {}

根据 imgList 数组绘画,具体使用方式跟上方的 drawgif.js 文件同理