解決在 IE11 下使用 canvas.toDataURL 報 SecurityError 的問題

問題

最近在項目中用到了 canvastoDataURL 方法來獲取圖片的 base64 格式數據,用以上傳到後臺。因爲以前也遇到過 canvas 被跨域圖片污染不能獲取數據的坑,所以這回一開始就機智的把 crossOrigin 屬性值加上,代碼大概以下:ajax

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 錯誤:canvas

clipboard.png

沒有具體的報錯信息,只經過提示定位到了執行 toDataURL 這一行,實在讓人疑惑。跨域

嘗試

第一時間 Google 了一下,發現不少人遇到這個問題,可是並無看到什麼有效的解決辦法,有些人建議使用 Fabric.js,可是看了一下以爲太麻煩了點。而在 caniuse 上,也明確標註了該方法在 IE11 上有問題。
看起來應該是 IE 上的一個 bug,因而想到了一個曲線救國的辦法:獲取圖片 base64 數據的辦法又不是隻有一個,既然 toDataURL 方法支持很差,那就用別的辦法:數組

  1. 先將 canvas 轉成 blob瀏覽器

  2. 再用 FileReaderdataUrl 的方式讀取安全

代碼大概以下:性能

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

然而這並無什麼卵用....
這回輪到了 msToBlob 方法報了 SecurityError 錯誤。我:???編碼

解決

看起來可能真的是安全緣由,惟一的安全緣由,只多是跨域圖片了。尋思着可能在 IE 上安全策略比較嚴格,即便設置了 crossOrigin = "Anonymous" 仍是不讓讀數據,因而想到了另一個思路,既然是由於跨域,那就把跨域因素去除:spa

  1. 使用 ajax 請求拿到圖片的二進制數據prototype

  2. 將二進制數據轉爲 base64 格式

  3. 將獲得的 base64 數據做爲圖片元素的 src 設置並畫到畫布上

  4. 正常調用 toDataURL

代碼大體以下:

// 以前的代碼
// ...
// 最後一行 imageElement.src = 'xxx' 替換:
getDataUrlBySrc('xxx').then(b64 => (imageElement.src = b64));

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. 將二進制字符串轉爲 base64 編碼的字符串
        const b64 = btoa(raw);
        
        const dataURL = "data:image/jpeg;base64," + b64;
        resolve(dataURL);
      };
      xmlHTTP.onerror = function(err) {
        reject(err);
      };
      xmlHTTP.send();
    } else {
      resolve(src);
    }
  });
}

嘗試了一下,成功達到了目的。
後來查閱資料得知,若是 canvas 污染了,那不管是 toDataURL 仍是 toBlob,都是沒法執行成功的。

缺陷

雖然這個方法能夠達到目的,可是卻犧牲了性能。要先請求一次圖片數據不說,數據編碼的轉換也是至關耗時的。小圖還好,若是圖片比較大,例如超過 3M ,那整個流程須要花費的時間能夠達到一兩分鐘,這是不可接受的。這裏筆者暫時還沒想到好的解決辦法,各位看官若是有更好的思路,還望告知。

相關文章
相關標籤/搜索