js实现组件截图功能踩坑记录

686 阅读2分钟

功能说明

最近公司想做个组件一键截图功能,本文记录一下相关的踩坑记录

实现方案

方案1: html2canvas

网上常见的通过dom进行截图的方案是html2canvas, 用法也非常简单。

官方demo:

<script type="text/javascript" src="../dist/html2canvas.js"></script>
<script type="text/javascript">
    html2canvas(document.body.getElementsByTagName("li")[0]).then(function(canvas) {
        document.body.appendChild(canvas);
        const img = canvas.toDataURL('image/png');
        const aDom = document.createElement('a');
        aDom.href = img;
        aDom.download = 'screenshot.png';
        aDom.click();
    });
</script>

demo截图效果

image.png

感觉很ok啊,直接在项目中尝试

结果发现有很严重的错位问题!!!

组件原图:

image.png

html2canvas截图效果:

image.png

image.png

html2canvas 原理

直接开始看html2canvas源码,发现其根据dom截图逻辑如下:

  1. clone一个不可见的根元素
  2. 解析需要截图的dom,获取dom的style,node, image, font等相关信息
  3. 将各个信息绘制到新建的canvas上
  4. 将canvas保存为图片

打断点进去看看问题:

我们先让html2Canvas创建的不可见的元素展示出来

image.png

蓝色区域为截图区域,可以很明显的发现,其与实际组件的位置存在错位。

那么为什么会有错位呢?继续靠两个dom的区别:

image.png

image.png

发现复制过后原来dom上的transform属性被设置成none了。

处理方案

handleBeforeCapture() {
        const { height: realHeight, width: realWidth } = (this.viewMain as HTMLDivElement).getBoundingClientRect();
        const [styleWidth, styleHeight] = [(this.viewMain as HTMLDivElement).style.width, (this.viewMain as HTMLDivElement).style.height];
        const [_, leftOffset, topOffset] = (this.viewMain as HTMLDivElement).style.transform.match(/translate\((.*)px, (.*)px\)/) || [];
        (this.viewMain as HTMLDivElement).style.width = realWidth / this.scale + "px";
        (this.viewMain as HTMLDivElement).style.height = realHeight / this.scale + "px";
        (this.viewMain as HTMLDivElement).style.left = leftOffset + "px";
        (this.viewMain as HTMLDivElement).style.top = topOffset + "px";
        (this.viewMain as HTMLDivElement).style.transform = "";
        return { styleWidth, styleHeight, topOffset, leftOffset };
    }

transform转变成topleft属性,即可实现正常截图。

方案2:监听paste

当用户通过浏览器的用户界面发起“粘贴”动作时,将触发 paste 事件

直接上代码

let items = event.clipboardData?.items;
let file = null;
if (items && items.length) {
    // 搜索剪切板items
    for (let i = 0; i < items.length; i++) {
        if (items[i].type.indexOf("image") !== -1) {
            file = items[i].getAsFile();
            break;
        }
    }
} else {
    this.$message.warning("当前浏览器不支持截图");
    return;
}
if (!file) {
    this.$message.warning("粘贴内容非图片");
    return;
}
upload(file)

60.gif

挺好用的哈哈