html2canvas 识别 css中 transform 和 boxshadow出现的问题及修正

1,186 阅读2分钟

1 问题描述

在项目中使用vue-grid-layout做可拖拽的模块,每个模块有截图下载的功能,使用了html2canvas(版本1.4.1)作为截图插件

/**
 * 组件下载
 */
export function componentDownload(domId, imgName) {
  setTimeout(() => {
    const targetDom = document.getElementById(domId);
    html2canvas(targetDom, {
      scale: 1, //处理模糊问题
      useCORS: true, //开启跨域,这个是必须的
      backgroundColor: `#002d66`,
    }).then((canvas) => {
      canvas.toBlob(function (blob) {
        saveAs(blob, imgName + '.png');
      });
    });
  }, 500);
}

问题1: 多数模块下载下来的截图是空白 问题2: 部分截图背景色不一致

image.png[问题2截图]

2问题定位

问题1: vue-grid-layout 可拖拽模块定位使用了css 中的transform: translate3d,html2canvas对 transform属性的支持尚有些问题

问题2: 原因大致和问题1相同,html2canvas对 boxshadow属性支持也是会出现问题

3问题解决

问题1

修改 html2canvas代码 src/render/stacking-context.ts,将getEffects改成下面的样子,其实就是移动了一下effects.unshift(...croplessEffects)位置,问题就解决了

getEffects(target: EffectTarget): IElementEffect[] {
        let inFlow = [POSITION.ABSOLUTE, POSITION.FIXED].indexOf(this.container.styles.position) === -1;
        let parent = this.parent;
        const effects = this.effects.slice(0);
        while (parent) {
            const croplessEffects = parent.effects.filter((effect) => !isClipEffect(effect));
            if (inFlow || parent.container.styles.position !== POSITION.STATIC || !parent.parent) {
                inFlow = [POSITION.ABSOLUTE, POSITION.FIXED].indexOf(parent.container.styles.position) === -1;
                if (parent.container.styles.overflowX !== OVERFLOW.VISIBLE) {
                    const borderBox = calculateBorderBoxPath(parent.curves);
                    const paddingBox = calculatePaddingBoxPath(parent.curves);
                    if (!equalPath(borderBox, paddingBox)) {
                        effects.unshift(
                            new ClipEffect(paddingBox, EffectTarget.BACKGROUND_BORDERS | EffectTarget.CONTENT)
                        );
                    }
                }
                effects.unshift(...croplessEffects);
            } else {
                effects.unshift(...croplessEffects);
            }
            parent = parent.parent;
        }
        return effects.filter((effect) => contains(effect.target, target));
    }

问题2

这个没有找到好的方法,就先将src/render/canvas/canvas-renderer.ts 关于boxShadow渲染的代码先注释掉了,对截图无伤大雅,注释代码如下

/** 
            styles.boxShadow
                .slice(0)
                .reverse()
                .forEach((shadow) => {
                    this.ctx.save();
                    const borderBoxArea = calculateBorderBoxPath(paint.curves);
                    const maskOffset = shadow.inset ? 0 : MASK_OFFSET;
                    const shadowPaintingArea = transformPath(
                        borderBoxArea,
                        -maskOffset + (shadow.inset ? 1 : -1) * shadow.spread.number,
                        (shadow.inset ? 1 : -1) * shadow.spread.number,
                        shadow.spread.number * (shadow.inset ? -2 : 2),
                        shadow.spread.number * (shadow.inset ? -2 : 2)
                    );

                    if (shadow.inset) {
                        this.path(borderBoxArea);
                        this.ctx.clip();
                        this.mask(shadowPaintingArea);
                    } else {
                        this.mask(borderBoxArea);
                        this.ctx.clip();
                        this.path(shadowPaintingArea);
                    }

                    this.ctx.shadowOffsetX = shadow.offsetX.number + maskOffset;
                    this.ctx.shadowOffsetY = shadow.offsetY.number;
                    this.ctx.shadowColor = asString(shadow.color);
                    this.ctx.shadowBlur = shadow.blur.number;
                    this.ctx.fillStyle = shadow.inset ? asString(shadow.color) : 'rgba(0,0,0,1)';

                    this.ctx.fill();
                    this.ctx.restore();
                });
          */

4 总结

这两个问题,我是通过查询网上资料配合直觉经验解决的,当然如果一点点去调试html2canvas代码,研究渲染的过程是个扎实的办法,并且能收获更多,但是项目周期不允许啊。

再有就是调试的时候,尽可能简化问题,我一开始用了很多模块的页面做调试,浪费了很多时间。