canvas的getImageData和toDataUrl跨域問題
背景是這樣的,母親節的時候,我們有個需求就是使用者可以長按或者點選一個按鈕進行截圖後去分享我們的活動,然而我們的圖片例如頭像,採用又拍雲做 cdn 優化,所以意味著圖片的連結跟主頁面所在域名不一樣,當需要需要對 canvas 圖片進行getImageData()或toDataURL()操作的時候,跨域問題就出來了。
對於跨域的圖片,只要能夠在網頁中正常顯示出來,就可以使用 canvas 的drawImage()API 繪製出來。但是如果你想更進一步,通過getImageData()方法獲取圖片的完整的畫素資訊,則多半會出錯。
舉例來說,使用下面程式碼獲取 github 上的自己頭像圖片資訊:
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瀏覽器下顯示如下錯誤:
Uncaught DOMException: Failed to execute ‘getImageData’ on ‘CanvasRenderingContext2D’: The canvas has been tainted by cross-origin data.
Firefox瀏覽器錯誤為:
SecurityError: The operation is insecure.
如果使用的是 canvas.toDataURL()方法,則會報:
Failed to execute ‘toDataURL’ on ’htmlCanvasElement’: Tainted canvased may not be exported
原因其實都是一樣的,跨域導致。
那有沒有什麼辦法可以解決這個問題呢?
可以試試crossOrigin屬性。
htmlcrossOrigin 屬性解決資源跨域問題
在 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屬性,和設定crossOrigin="use-credentials"在預設情況下都會報跨域出錯,但是性質上卻不一樣,兩者有較大區別。
crossOrigin 相容性
IE11+(IE Edge),Safari,Chrome,Firefox 瀏覽器均支援,IE9 和 IE10 會報 SecurityError 安全錯誤,如下截圖:
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 真實採坑記和建議
廣州vi設計公司 http://www.maiqicn.com 我的007辦公資源網 https://www.wode007.com
如果使用vue做資料渲染,不要在生成頁做太多資料處理的操作,提前把動態資料處理好,否則即便用 $nextTick 也會有在生成圖片時資料不完整的情況
引用 CDN 上的圖片,需要設定 useCORS 為 true,同時要保證所有圖片載入完成後再生成,可使用 new Imaage 做預載入和判斷是否全部 load
用背景 background,生成的圖片清晰度不夠,會模糊;用 img 引入的方式可避免這個問題
在 iOS 系統的 13.4.1,無法生成圖片,需要退回到 1.0.0-rc.4 版本,不要使用 1.0.0-rc.5 版本,issues 地址:https://github.com/niklasvh/html2canvas/issues/2205
可把生成的圖片設定透明度 opacity 為 0,蓋在原有元素之上,便於在微信儲存,不會因為生成的圖和原有元素略微有差距,而抖動。