过去几个月中有两个需求都用到了html2canvas
插件,目的是使Web中的DOM结构转为图片,但由于html2canvas
的原理限制,遇到了一系列问题:
跨域问题
html2canvas
转图片的原理是将传入的dom元素渲染到canvas画布上,然后利用canvas的toDataURL方式转位图片的。然而html2canvas
在将dom元素绘制在canvas上时,如下html2canvas加载图片的核心代码所示:
return await new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
//ios safari 10.3 taints canvas with data urls unless crossOrigin is set to anonymous
if (isInlineBase64Image(src) || useCORS) {
img.crossOrigin = 'anonymous';
}
img.src = src;
if (img.complete === true) {
// Inline XML images may fail to parse, throwing an Error later on
setTimeout(() => resolve(img), 500);
}
if (this._options.imageTimeout > 0) {
setTimeout(
() => reject(`Timed out (${this._options.imageTimeout}ms) loading image`),
this._options.imageTimeout
);
}
});
对于img会重新发起网络请求获取图片,因为是js发起的请求,所以对于有跨域限制的图片会提示跨域错误,而且可以注意到当传入的options
参数设置useCORS
时,是会设置img.crossOrigin = 'anonymous'
的。
要想解决这个问题,最简单的方法就是在图片服务器上进行设置允许跨域,但是这种方式在服务器自己不可控的情况下是行不通的。所以在解决问题的过程中就想着是否有前端侧的方法来避免这个问题呢?网上有很多文章介绍说可以给图片设置crossorigin="anonymous"
、html2canvas
的option设置userCORS: true
、提前把跨域的网络图片转为base64等方法来解决,但是经过一系列折腾后发现,这些方法都是需要服务端的支持才行得通的。可行的方法主要包括:
- 服务端配置跨域访问
- 设置代理服务器
- 图片数量固定且不多的情况下可选择放置在项目中
图片显示不完整问题
当dom元素有滚动条时在生成图片后可能会出现图片不完整,部分区域是空白的情况,可以调用图下代码 dom.scrollTo(0, dom.scrollHeight)
控制滚动条至页面底部解决。另外还可能出现图片如果有偏移的话,可添加配置scrollY:0, scrollX:0,
解决。
所生成图片边缘有缝隙/空白问题
由于不同设备的屏幕像素密度不一样,html2canvas在计算宽高的时候可能出现了误差导致。解决方法:
- 设置宽度或高度减去几个像素,刚好把空白的地方裁掉(当然前提是在不影响图片内容的情况下)
- 如果是纯色背景,可以设置
html2cavas
配置里的背景色为相同颜色即可
透明图片异常显示的问题
很奇怪的异常,最开始这个图片是以背景图方式显示的,最后换成img标签形式展示就没这个问题了。
由dom元素的动画引起的显示不完整问题
问题的根因是html2canvas
在解析传入的dom节点之前会先去克隆一个副本,这个操作的过程可能就重新触发了动画的播放条件,于是html2cavas
在将dom绘制至canvas时可能只是截取到了动画过程中的一个状态。解决这个问题的本质是要在html2cavans
绘制dom时,dom的动画是不执行的或者动画已经执行完毕,具体解决方法包括:
- 另外创建一个不可视且无动画版本的dom作为
html2canvas
的入参 - 利用定时器保证在动画播放完毕时再执行
html2canvas
- 声明一个css类,设置规则
animation-duration: 0s !important; animation-delay: 0s !important;
使动画执行时间可延迟时间都为0s,这样dom会直接处于动画执行完毕的状态,当执行html2canvas之前动态添加这个类,当执行完毕后再删除掉这个类,不影响后续动画的执行。
Comments | NOTHING