欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

使用 html2canvas 和 gif.js 实现表情符号制作

最编程 2024-03-17 15:47:58
...

使用html2canvas和gif.js实现表情包制作

记录一下过年前做的一个需求:

用户输入名字,然后生成表情包。

image.png 上面的“XX给您”(静态)是根据输入的名字变化,而下面的小人(动态的)只能切换男版或者女版。

设计师给到我的是两张动图,我的想法是取到两张动图的每一帧(随便搜一个网站把动图传上去就能取到了),用户输入文字后,将文字与每一帧图片合成转换为新的帧,然后再将新的帧全部连起来形成动图。

先大概了解一下html2canvas和gif.js的用法,这两个库都很简洁,可以很快地上手。

html2canvas

很简单,只要将需要转换的dom元素传入html2canvas(element, options)方法中即可。

html2canvas(document.body).then(function(canvas) {
    document.body.appendChild(canvas);
});

options中比较常用到的属性是backgroundColor:画布背景颜色。

gif.js

gif.js可以直接将image/canvas,或者ctx中获得帧,得到gif

它有四个核心方法:

//1.创建GIF实例
var gif = new GIF({
    workers: 2, //启用两个worker
    quality: 10, //图像质量
    workerScript: './gif.worker.js' //worker
    transparent:'#fff' //这个在背景图片是透明时会用到
})
//2.向gif中添加帧图片,参数可以是image、canvas、ctx
gif.addFrame(frame, {
    copy: true, //复制像素数据
    delay: 80, //帧停留时间80ms
    //还有其他选项如width、height,用来设置图片的宽高
})
//3.转换完成后的回调
gif.on('finished',function(blob) {
	//do something
})
//4.启动实例(开始生成gif)
gif.render();

1.将文字与图片拼凑在一起

用户输入名字,点击预览后,将文字与每一帧图片拼凑在一起形成新的帧图片。

要获取每一帧,就需要将每一帧放到页面上,然后通过html2canvas转换为图片(或者canvas或者其上下文)

在这里我将文字与第一帧图片放到页面的临时dom结构中,将该dom结构转为ctx后,再将图片替换为下一帧,以此类推。

这个临时dom结构如下:

<div v-cloak id="temp">
    <div id="temp-text" ref="tempText" style="opacity: 0;">{{name}}给您</div>
    </div>

由于这部分对于用户来说是不可见的,于是设置了绝对定位,并且设置z-index为-1,避免对页面的原本布局产生影响。

这里不可以用opacity、visibility或display来隐藏元素,因为会导致html2canvas绘制出来的是空白的。

2.创建Gif实例

var running = false; //标记worker的状态
var gifBlob = ''; //存放gif的blob格式
var gifBase = ''; //存放gif的base64格式

var gif = new GIF({
  workers: 2,
  quality: 10,
  workerScript: './js/gif.worker.js',
  transparent: '#fff'
});

//转换成功后的操作,主要是做预览,并方便用户进行保存和转发。
gif.on('finished', function (blob) {
  //生成图片
  gifBlob = blob;
  //将图片转为base64并展示到页面中
  blobToDataURI(gifBlob,(result) => {
    gifBase = result;
    document.getElementById('pic').src = gifBase;
    document.getElementById('gifHidden').src = gifBase;
  })
    running = false;
})

3.将拼凑后的dom结构转为canvas,并添加到Gif实例中,转为gif

/* 
 * imgList:原始动图(未拼凑时)的序列,需要根据选择的性别传入不同的值
 * temp:存放用于转换为canvas的临时dom节点
 * onerror:错误处理函数
 */
function loadGif(imgList, temp, onerror) {
  if(running == true) {onerror();return;} //防止用户多次点击,如果worker正在运行,通过onerror方法提示用户正在生成中。
  running = true;
let imgImage = new Image();
let count = 1;
function imageToCanvas(img,src) { //img为序列帧
  temp.appendChild(img);//将帧插入临时dom
  img.crossorigin = "anonymous"
  img.src = src;
  img.onload = function() { //图片加载成功事件
    html2canvas(document.getElementById('temp'),{backgroundColor: 'transparent'}).then(canvas => {
      canvas.style.backgroundColor = "transparent"
      let ctx = canvas.getContext('2d', { //获取二维渲染上下文
        alpha: true
      })
      gif.addFrame(ctx, { //这里我用了上下文,因为直接添加画布,会导致生成的图片有锯齿。
        copy: true,
        delay: 80,
        width: Math.floor(canvas.width),
        height: Math.floor(canvas.height)
      });
       //移除图片
      img.remove();
      count ++;
      if(count === 6) { //一共有5帧,count等于6时开始转换
        gif.render();
      }else {
        imgImage = null;
        imgImage = new Image();
          //开始转换下一帧
        imageToCanvas(imgImage, imgList[count - 1])
      }
    })
  }
}
    imageToCanvas(imgImage, imgList[0])
}

以上就是整个的实现过程。

再记录下过程中遇到的一些问题

1.首先是一开始生成的动图背景是白色的,但是我原图都是透明底色。那问题肯定是出在从html转canvas或者从canvas转gif的过程。

image.png

然后我将html转canvas后的每一张图片都显示出来,发现转换后的图片背景颜色都是白色,问题应该就是出在这里。查了文档之后,

尝试在options中加入backgroundColor: 'transparent',即:

html2canvas(document.getElementById('temp'),{backgroundColor: 'transparent'}).then(...)

设置完了之后再试一次,发现背景颜色变成了黑色。。。

image.png

再查看每一帧图片,都是透明底色的,那就是在转gif时出了问题。

再去看了gif.js的文档,发现有个参数:background、transparent

background在文档中的解释是:background color where source image is transparent。看到这个之后我激动了一下,那我把这个属性设置成transparent不就可以了?嗯。。显然还是不行,背景还是黑色。

image.png

于是我看了看另一个属性。transparent在文档中的解释是:透明的16进制色值,看完后一脸懵逼,这几个词都懂,但是组合在一起,就不知道这个属性有什么鬼用,但是隐约觉得跟我这次要解决的问题有关。所以我又修改了代码

image.png

成功!根据这个结果,我猜测了一下这个属性的意思应该是将底色为该值的颜色变为透明。因为当我不设置背景色时,为白色,然后添加transparent: '#fff'后,底色就是透明了,也就是将白色变为了透明。

image.png

2.解决了背景颜色透明的问题,又出现了生成的图片尺寸在ios13系统下不一样的问题。捣鼓了很久,一直以为是浏览器兼容问题。

image.png

最后发现ios13的dpr=3,即时生成的图片宽高与dpr=2一致,但内容不全。那么就需要将图片尺寸等比例放大。通过将宽高(根据dpr换算)传入构造函数解决:

var gif = new GIF({
  workers: 2,
  quality: 10,
  workerScript: './js/gif.worker.js',
  transparent: '#fff'
  /*
   原本的width是7.9rem和9.4rem,为了兼容dpr=3的设备,进行了换算
  */
  width: 7.9 * window.rem * window.devicePixelRatio / 2,
  height: 9.4 * window.rem * window.devicePixelRatio / 2,
});

3.最后一个问题,还是出现在ios13系统下,生成的动图是斜的。

image.png

我将每一帧的图片都显示到页面上 ,发现每一帧都是正常的,所以应该是从帧图片转换到gif的过程中出问题的。

问题在于在执行git.addFrame时,因为传的是canvas上下文,在gif.js源码中,会默认将每一帧的 宽高设置为构造函数传入的宽高,按道理从构造函数中传入的宽高已经经过换算,所以我不清楚内部是做了什么转换,导致图片尺寸出了问题。

最后的解决方案:修改了源码,从addFrame的options参数中传入宽高(画布的宽高),作为帧序列图片的宽高。问题得到解决。因为每一帧的大小都一样,所以从构造函数中传入的宽高也已经没用了,也就是问题3与问题2最后只需要这一处修改就可以解决:

gif.addFrame(ctx, {
        copy: true,
        delay: 80,
        width: Math.floor(canvas.width),
        height: Math.floor(canvas.height)
      });