视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
IE11下使用canvas.toDataURL报SecurityError错误的解决方法
2020-11-27 22:25:33 责编:小采
文档


发现问题

最近在项目中用到了 canvas 的 toDataURL 方法来获取图片的 base 格式数据,用以上传到后台。由于之前也遇到过 canvas 被跨域图片污染不能获取数据的坑,因此这回一开始就机智的把 crossOrigin 属性值加上,代码大概如下:

const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
const imageElement = document.createElement("img");
imageElement.crossOrigin = "Anonymous";
imageElement.onload = () => {
 context.drawImage(
 imageElement,
 params.left,
 params.top,
 canvas.width,
 canvas.height,
 0,
 0,
 canvas.width,
 canvas.height
 );

 const dataUrl = canvas.toDataURL("image/jpeg", 1);
}
imageElement.src = 'xxx';

本以为万无一失,而在 Chrome 浏览器上面也非常顺利;然而到了 IE11 上,却出现了一个莫名其妙的 SecurityError 错误:

没有具体的报错信息,只通过提示定位到了执行 toDataURL 这一行,实在让人疑惑。

尝试

第一时间 Google 了一下,发现很多人遇到这个问题,但是并没有看到什么有效的解决办法,有些人建议使用 Fabric.js,但是看了一下觉得太麻烦了点。而在 caniuse 上,也明确标注了该方法在 IE11 上有问题。

看起来应该是 IE 上的一个 bug,于是想到了一个曲线救国的办法:获取图片 base 数据的办法又不是只有一个,既然 toDataURL 方法支持不好,那就用别的办法:

  • 先将 canvas 转成 blob
  • 再用 FileReader 以 dataUrl 的方式读取
  • 代码大概如下:

    const reader = new FileReader();
    reader.readAsDataURL(canvas.msToBlob());
    reader.onloadend = () => {
     const basedata = reader.result;
    };

    然而这并没有什么卵用....

    这回轮到了 msToBlob 方法报了 SecurityError 错误。我:???

    解决

    看起来可能真的是安全原因,唯一的安全原因,只可能是跨域图片了。寻思着可能在 IE 上安全策略比较严格,即使设置了 crossOrigin = "Anonymous" 还是不让读数据,于是想到了另外一个思路,既然是因为跨域,那就把跨域因素去除:

  • 使用 ajax 请求拿到图片的二进制数据
  • 将二进制数据转为 base 格式
  • 将得到的 base 数据作为图片元素的 src 设置并画到画布上
  • 正常调用 toDataURL
  • 代码大致如下:

    // 之前的代码
    // ...
    // 最后一行 imageElement.src = 'xxx' 替换:
    getDataUrlBySrc('xxx').then(b => (imageElement.src = b));
    
    function getDataUrlBySrc(src: string) {
     return new Promise<string>((resolve, reject) => {
     if (Cache.localGet("isIE")) {
     const xmlHTTP = new XMLHttpRequest();
     xmlHTTP.open("GET", src, true);
    
     // 以 ArrayBuffer 的形式返回数据
     xmlHTTP.responseType = "arraybuffer";
    
     xmlHTTP.onload = function(e) {
     
     // 1. 将返回的数据存储在一个 8 位无符号整数值的类型化数组里面
     const arr = new Uint8Array(xmlHTTP.response);
     
     // 2. 转为 charCode 字符串
     const raw = Array.prototype.map
     .call(arr, charCode => String.fromCharCode(charCode))
     .join("");
     
     // 3. 将二进制字符串转为 base 编码的字符串
     const b = btoa(raw);
     
     const dataURL = "data:image/jpeg;base," + b;
     resolve(dataURL);
     };
     xmlHTTP.onerror = function(err) {
     reject(err);
     };
     xmlHTTP.send();
     } else {
     resolve(src);
     }
     });
    }

    尝试了一下,成功达到了目的。

    后来查阅资料得知,如果 canvas 污染了,那无论是 toDataURL 还是 toBlob,都是无法执行成功的。

    缺陷

    虽然这个方法可以达到目的,但是却牺牲了性能。要先请求一次图片数据不说,数据编码的转换也是相当耗时的。小图还好,如果图片比较大,例如超过 3M ,那整个流程需要花费的时间可以达到一两分钟,这是不可接受的。

    canvas的toDataUrl是否会压缩图像?

    使用canvas的drawImage方法将image对象draw到canvas画布上时,图片大小会显著增加,并且只能保存为PNG格式。

    将canvas用toDataUrl转化为base,即使encoderOptions设置为1,图片也会有较大幅度的减小,但是比起最初的image还是要大。如果encoderOptions使用默认的0.92,最终的图片大小和初始的是相差不多的

    总结

    下载本文
    显示全文
    专题