背景是這樣的,母親節的時候,我們有個需求就是用戶可以長按或者點擊一個按鈕進行截圖後去分享我們的活動,然而我們的圖片例如頭像,採用又拍雲做 cdn 優化,所以意味著圖片的鏈接跟主頁面所在功能變數名稱不一樣,當需要需要對 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
屬性。
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
屬性,和設置 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 真實採坑記和建議
- 如果使用 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,蓋在原有元素之上,便於在微信保存,不會因為生成的圖和原有元素略微有差距,而抖動。