最近在項目中用到了 canvas
的 toDataURL
方法來獲取圖片的 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
沒有具體的報錯信息,只經過提示定位到了執行 toDataURL
這一行,實在讓人疑惑。跨域
第一時間 Google 了一下,發現不少人遇到這個問題,可是並無看到什麼有效的解決辦法,有些人建議使用 Fabric.js,可是看了一下以爲太麻煩了點。而在 caniuse 上,也明確標註了該方法在 IE11 上有問題。
看起來應該是 IE 上的一個 bug,因而想到了一個曲線救國的辦法:獲取圖片 base64
數據的辦法又不是隻有一個,既然 toDataURL
方法支持很差,那就用別的辦法:數組
先將 canvas
轉成 blob
瀏覽器
再用 FileReader
以 dataUrl
的方式讀取安全
代碼大概以下:性能
const reader = new FileReader(); reader.readAsDataURL(canvas.msToBlob()); reader.onloadend = () => { const base64data = reader.result; };
然而這並無什麼卵用....
這回輪到了 msToBlob
方法報了 SecurityError
錯誤。我:???編碼
看起來可能真的是安全緣由,惟一的安全緣由,只多是跨域圖片了。尋思着可能在 IE 上安全策略比較嚴格,即便設置了 crossOrigin = "Anonymous"
仍是不讓讀數據,因而想到了另一個思路,既然是由於跨域,那就把跨域因素去除:spa
使用 ajax
請求拿到圖片的二進制數據prototype
將二進制數據轉爲 base64
格式
將獲得的 base64
數據做爲圖片元素的 src
設置並畫到畫布上
正常調用 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 ,那整個流程須要花費的時間能夠達到一兩分鐘,這是不可接受的。這裏筆者暫時還沒想到好的解決辦法,各位看官若是有更好的思路,還望告知。