功能说明
最近公司想做个组件一键截图功能,本文记录一下相关的踩坑记录
实现方案
方案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截图效果
感觉很ok啊,直接在项目中尝试
结果发现有很严重的错位问题!!!
组件原图:
html2canvas截图效果:
html2canvas 原理
直接开始看html2canvas源码,发现其根据dom截图逻辑如下:
- clone一个不可见的根元素
- 解析需要截图的dom,获取dom的style,node, image, font等相关信息
- 将各个信息绘制到新建的canvas上
- 将canvas保存为图片
打断点进去看看问题:
我们先让html2Canvas创建的不可见的元素展示出来
蓝色区域为截图区域,可以很明显的发现,其与实际组件的位置存在错位。
那么为什么会有错位呢?继续靠两个dom的区别:
发现复制过后原来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转变成top和left属性,即可实现正常截图。
方案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)
挺好用的哈哈