背景
html2canvas 是一个 HTML 渲染器。该脚本允许你直接在用户浏览器截取页面或部分网页的“屏幕截屏”,屏幕截图是基于 DOM,因此生成的图片并不一定 100% 一致,因为它没有制作实际的屏幕截图,而是根据页面上可用的信息构建屏幕截图。
由于整个绘制过程时在客户端完成的,所以客户端的一些限制也同样应用于html2canvas,对图片跨域问题也归咎于canvas对图片的跨域问题,在解决问题之前,我们先来了解一些基本知识
img标签的crossorigin属性
crossorigin
属性标记浏览器使用跨域请求图片资源,需要服务器配合使用,就可以在canvas里使用该图片,crossorigin
可选值有两个
- crossorigin="anonymous"
当配置为 anonymous
值时,需要在返回的图片响应头设置 Access-Control-Allow-Origin
为 *
,否则会报以下错误
Access to img at 'xxx.jpg' from origin 'null' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
- crossorigin="use-credentials"
当配置为 use-credentials 值时,响应头 Access-Control-Allow-Origin
则不能设置为 *
,必须指定白名单,否则会报以下错误
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*'
when the request's credentials mode is 'include'.
在canvas使用到图片的场景下,如果从外部引入的 HTML <img> 或 SVG <svg> ,图像源不符合跨域规则,将会被阻止从 <canvas> 中读取数据。当调用 getImageData()
,toBlob()
,toDataURL()
,则会报以下错误,以 getImageData
为例
Uncaught SecurityError: Failed to execute 'getImageData' on 'CanvasRenderingContext2D':
The canvas has been tainted by cross-origin data.
at https://cdpn.io/cp/internal/boomboom/pen.js?key=pen.js-85eaccd5-18d4-330a-d25b-20997e8ad314:12
See the Pen Untitled by kelen (@imkelen) on CodePen.
解决方法也很简单,配置 corssorigin
属性就可以了
// 加入这行代码即可成功调用 getImageData() 方法
img.setAttribute("crossorigin", "anonymous");
//or
img.crossOrigin = "Anonymous";
代理转发
如果是自己的图片服务器,那么该方法就可以解决canvas的图片跨域问题,但是在日常开发难免会使用第三方的图片,无法在图片响应头配置跨域字段,可以通过代理来实现。
这里我们使用一个第三方的nodejs服务,html2canvas-proxy-nodejs ,源码很简单
app.get('/', cors(), validUrl, (req, res, next) => {
switch (req.query.responseType) {
case 'blob':
req.pipe(request(req.query.url).on('error', next)).pipe(res);
break;
case 'text':
default:
request({ url: req.query.url, encoding: 'binary' }, (error, response, body) => {
if (error) {
return next(error);
}
res.send(
`data:${response.headers['content-type']};base64,${Buffer.from(
body,
'binary',
).toString('base64')}`,
);
});
}
});
原理是服务端无跨域限制,通过 request 库请求图片资源,然后再转为base64后,发送给客户端,同时通过 cors 中间件设置了跨域的响应头
最后在html2canvas配置 proxy
选项即可
html2canvas(document.body, {
proxy: 'https://www.html2canvas-server.com', // 替换成 html2canvas-proxy 服务的域名即可
}).then(function(canvas) {
document.body.appendChild(canvas);
});