背景

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);
});