背景
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 使用到图片的场景下,如果从外部引入的 <img />
或 <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
解决方法也很简单,配置 corssorigin
属性就可以了
// 加入这行代码即可成功调用 getImageData() 方法img.setAttribute("crossorigin", "anonymous");//orimg.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);});