html2canvas 生成图片踩坑记

7,809

本次做h5 活动,遇到一个需求,点击button 把html 生成图片,长按保存功能 由于html的结构比较复杂,使用canvas 直接画相对来说增加了不小工作量,所以使用html2canvas ,html2canvas的使用比较简单,但是坑也很多

老生常谈的跨域问题

谈到跨域问题,应该只要入了canvas toDataURL 转成图片的坑就应该遇到过这个问题,如果不进行跨域处理生成图片不能跨域的图片会空白处理 这个没有好的解决方案, 图片服务器需要配置Access-Control-Allow-Origin 由于公司的开发build 与 js 是两个团队在做,build 团队的图片地址都是不可跨域的,而且页面所以的图片设置都是background image 的形式,针对这个问题我做了如下处理

  1. 把之前build 图片下载重新上传到支持跨域的cnd 服务器
  2. 把需要生成图片的html css 样式背景图片进行重写

当然这样还没有结束,配置html2canvas

html2canvas(dom, {
        backgroundColor: null,
        canvas,
        //allowTaint: true,
        useCORS: true
    })

useCORS 设置允许跨域,特别提醒 allowTaint 也是允许跨域,但是这个允许跨域只是允许你跨域生成canvas ,但是还是不能把canvas转化成图片,要想解决toDataURL 的跨域问题,还需要使用useCORS。但是这两个属性不能共生

生成的图片模糊

把scale 设置成二倍直接上代码

 * 根据window.devicePixelRatio获取像素比
 */
function DPR() {
    if (window.devicePixelRatio && window.devicePixelRatio > 1) {
        return window.devicePixelRatio;
    }
    return 1;
}

.....

  // DOM 节点计算后宽高
    const width = parseValue(box.width);
    const height = parseValue(box.height);
    // 获取像素比
    const scaleBy = DPR()*2;
    // 创建自定义 canvas 元素
    var canvas = document.createElement('canvas');
    // 设定 canvas 元素属性宽高为 DOM 节点宽高 * 像素比
    canvas.width = width * scaleBy;
    canvas.height = height * scaleBy;
    // 设定 canvas css宽高为 DOM 节点宽高
    canvas.style.width = `${width}px`;
    canvas.style.height = `${height}px`;
     // 获取画笔
    const context = canvas.getContext('2d');

    // 将所有绘制内容放大像素比倍
    context.scale(scaleBy, scaleBy);
    // imageSmoothingEnabled Canvas 2D API 用来设置图片是否平滑的属性,true表示图片平滑(默认值),false表示图片不平滑,默认的改变大小的算法会造成图片模糊并且破坏图片原有的像素。 如果那样的话,设置属性值为false。
    context.mozImageSmoothingEnabled = false;
    context.webkitImageSmoothingEnabled = false;
    context.msImageSmoothingEnabled = false;
    context.imageSmoothingEnabled = false;
.....

html2canvas(dom, {
        backgroundColor: null,
        canvas,
        width: width, // 设置width
        height: height, // 设置height
        scale: scaleBy,// 设置scaleBy
        useCORS: true
    })

过程中使用的雪碧图最容易出现模糊的情况,所以图片需要使用2倍图

生成图片过程中会出现html闪现

可能这个题目不是很好的描述问题。我先描述一下问题所在,由于html2canvas 生成图片所在的html 必须是真实存在的,否则生成canvas为空白。也就是需要生成html不能设置 disabled: none; visibility: hidden; 等属性。 因此表明在调用html2canvas 生成canvas 过程中必须dom 节点渲染完成。因此这就会导致在生成canvas 会出现原有html 的闪现 这个问题其实也比较好解决,用了一个小技巧,使用top 属性,把html 移除视野 top:100% 当然这个解决方案比较多,目前我用的是这个

动态渲染html 生成canvas会出现样式错乱

这个问题,本身需要生成的html 中有一部分是轮播图,这样html dom 会动态渲染, 解决方案很简单,取消轮播图,直接用 v-if(注:工程使用vue 开发)判定进行html 渲染,取消轮播

生成的图片background-repeat: no-repeat;不生效,导致图片重复

这个问题我也没找到原因,结果就是虽然设置了background-repeat: no-repeat; 但是图片底部会出现大约1像素的重复。 由于没有找到根本原因,使用了最直接的的方案解决,直接把图片高度增加2像素,成功避免了这个问题。(哈哈哈哈,我真是一个投机取巧的鬼才)

html2canvas 不支持 display: -webkit-box

由于代码中有段文字需要多行末尾省略号,所以出现如下css

    display: -webkit-box;
    overflow: hidden;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    text-overflow: ellipsis;

在html2canvas中,会将css中的display中的值映射成一个数字,而在下图的映射表中,只要找到了flex的映射值,并没有--webkit-flex的映射值,所以-webkit-flex被映射成了DISPLAY.NONE,从而导致了isVisible的计算值返回了false,最终导致了无法生成想要的canvas

解决方案不使用flex布局,js 实现文字溢出,此方案仅限于文字个数具体的情况下,所以比较局限,大家有好的方案欢迎补充 css

height:2倍的line-height; /*根据实际需求更改*/ 
display:inline-block;

js

contentText(num){
        const ellipsis = oldContentText.length > num?'...':''
        return oldContentText.slice(0,num) + ellipsis
  }