canvas 的 getImageData 和 toDataUrl 跨域问题

背景是这样的,母亲节的时候,咱们有个需求就是用户能够长按或者点击一个按钮进行截图后去分享咱们的活动,然而咱们的图片例如头像,采用又拍云作 cdn 优化,因此意味着图片的连接跟主页面所在域名不同,当须要须要对 canvas 图片进行 getImageData()toDataURL() 操做的时候,跨域问题就出来了。html

对于跨域的图片,只要可以在网页中正常显示出来,就可使用 canvas 的 drawImage() API 绘制出来。可是若是你想更进一步,经过 getImageData() 方法获取图片的完整的像素信息,则多半会出错。前端

举例来讲,使用下面代码获取 github 上的本身头像图片信息:vue

var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');

var img = new Image();
img.onload = function () {
    context.drawImage(this, 0, 0);
    context.getImageData(0, 0, this.width, this.height);
};
img.src = 'https://avatars3.githubusercontent.com/u/496048?s=120&v=4';';

结果在 Chrome 浏览器下显示以下错误:nginx

Uncaught DOMException: Failed to execute ‘getImageData’ on ‘CanvasRenderingContext2D’: The canvas has been tainted by cross-origin data.git

出错信息截图

Firefox 浏览器错误为:github

SecurityError: The operation is insecure.web

若是使用的是 canvas.toDataURL()方法,则会报:ajax

Failed to execute ‘toDataURL’ on ’HTMLCanvasElement’: Tainted canvased may not be exportedcanvas

缘由其实都是同样的,跨域致使。后端

那有没有什么办法能够解决这个问题呢?

能够试试 crossOrigin 属性。

HTML crossOrigin 属性解决资源跨域问题

在 HTML5 中,有些元素提供了支持 CORS(Cross-Origin Resource Sharing)(跨域资源共享)的属性,这些元素包括 ,`` 等,而提供的属性名就是 crossOrigin 属性。

所以,上面的跨域问题能够这么处理:

var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');

var img = new Image();
img.crossOrigin = '';
img.onload = function () {
    context.drawImage(this, 0, 0);
    context.getImageData(0, 0, this.width, this.height);
};
img.src = 'https://avatars3.githubusercontent.com/u/496048?s=120&v=4';';

增长一个 img.crossOrigin = '' 便可,虽然 JS 代码这里设置的是空字符串,实际上起做用的属性值是 anonymous

crossOrigin 能够有下面两个值:

关键字 释义
anonymous 元素的跨域资源请求不须要凭证标志设置。
use-credentials 元素的跨域资源请求须要凭证标志设置,意味着该请求须要提供凭证。

其中,只要 crossOrigin 的属性值不是 use-credentials,所有都会解析为 anonymous,包括空字符串,包括相似 'abc' 这样的字符。

例如:

img.crossOrigin = 'abc';
console.log(img.crossOrigin);    // 结果是'anonymous'

crossOrigin 解析为 anonymous

另外还有一点须要注意,那就是虽然没有 crossOrigin 属性,和设置 crossOrigin="use-credentials" 在默认状况下都会报跨域出错,可是性质上却不同,二者有较大区别。

crossOrigin 兼容性

IE11+(IE Edge),Safari,Chrome,Firefox 浏览器均支持,IE9 和 IE10 会报 SecurityError 安全错误,以下截图:

img

crossOrigin 属性为何能够解决资源跨域问题?

crossOrigin=anonymous 相对于告诉对方服务器,你不须要带任何非匿名信息过来。例如 cookie,所以,当前浏览器确定是安全的。

就比如你要去别人家里拿一件衣服,crossOrigin=anonymous 相对于告诉对方,我只要衣服,其余都不要。若是不说,可能对方在衣服里放个窃听的工具什么的,就不安全了,浏览器就会阻止。

下载到本地

IE10 浏览器不支持 crossOrigin 怎么办?

咱们请求图片的时候,不是直接经过 new Image(),而是借助 ajax 和 URL.createObjectURL() 方法曲线救国。

代码以下:

var xhr = new XMLHttpRequest();
xhr.onload = function () {
    var url = URL.createObjectURL(this.response);
    var img = new Image();
    img.onload = function () {
        // 此时你就可使用canvas对img随心所欲了
        // ... code ...
        // 图片用完后记得释放内存
        URL.revokeObjectURL(url);
    };
    img.src = url;
};
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.send();

此方法不只 IE10 浏览器 OK,本来支持 crossOrigin 的诸位浏览器也是支持的。

也就多走一个 ajax 请求,还能够!

根据,根据实践发现,在 IE 浏览器下,若是请求的图片过大,几千像素那种,图片会加载失败,我猜是超过了 blob 尺寸限制。

后来采用的解决方案是:把图片下载到本地(前端或者是后端均可以,最后采用我前端来作)

getAvator(user, func) {
      window.URL = window.URL || window.webkitURL;  // Take care of vendor prefixes.
      var xhr = new XMLHttpRequest();
      xhr.open('GET', user.avatar, true);
      xhr.responseType = 'blob';
      xhr.send()

      xhr.onload = function(e) {
        const {target} = e
        const {status, response, readyState} = target
        if (readyState == 4 && status == 200) {
          var blob = response;
          var img = document.createElement('img');
          img.classList.add("avatar")
          var reader = new window.FileReader();
          reader.readAsDataURL(blob);
          reader.onloadend = function() {
            var base64data = reader.result;
            img.src = base64data;
          };
          func && func(img)
        }
      };
    },

设置 nginx 代理

如 PHP 添加响应头信息,* 通配符表示容许任意域名:

header("Access-Control-Allow-Origin: *");

或者指定域名:

header("Access-Control-Allow-Origin: www.zhangxinxu.com");

html2canvas 真实采坑记和建议

  1. 若是使用 vue 作数据渲染,不要在生成页作太多数据处理的操做,提早把动态数据处理好,不然即使用 $nextTick 也会有在生成图片时数据不完整的状况
  2. 引用 CDN 上的图片,须要设置 useCORS 为 true,同时要保证全部图片加载完成后再生成,可以使用 new Imaage 作预加载和判断是否所有 load
  3. 用背景 background,生成的图片清晰度不够,会模糊;用 img 引入的方式可避免这个问题
  4. 在 iOS 系统的 13.4.1,没法生成图片,须要退回到 1.0.0-rc.4 版本,不要使用 1.0.0-rc.5 版本,issues 地址:https://github.com/niklasvh/html2canvas/issues/2205
  5. 可把生成的图片设置透明度 opacity 为 0,盖在原有元素之上,便于在微信保存,不会由于生成的图和原有元素略微有差距,而抖动。
相关文章
相关标签/搜索