canvas 的 getImageData 和 toDataUrl 跨域問題

背景是這樣的,母親節的時候,咱們有個需求就是用戶能夠長按或者點擊一個按鈕進行截圖後去分享咱們的活動,然而咱們的圖片例如頭像,採用又拍雲作 cdn 優化,因此意味着圖片的連接跟主頁面所在域名不同,當須要須要對 canvas 圖片進行 getImageData()toDataURL() 操做的時候,跨域問題就出來了。html

對於跨域的圖片,只要可以在網頁中正常顯示出來,就可使用 canvas 的 drawImage() API 繪製出來。可是若是你想更進一步,經過 getImageData() 方法獲取圖片的完整的像素信息,則多半會出錯。前端

舉例來講,使用下面代碼獲取 github 上的本身頭像圖片信息:vue

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 瀏覽器下顯示以下錯誤:nginx

Uncaught DOMException: Failed to execute ‘getImageData’ on ‘CanvasRenderingContext2D’: The canvas has been tainted by cross-origin data.git

出錯信息截圖

Firefox 瀏覽器錯誤爲:github

SecurityError: The operation is insecure.web

若是使用的是 canvas.toDataURL()方法,則會報:ajax

Failed to execute ‘toDataURL’ on ’HTMLCanvasElement’: Tainted canvased may not be exportedcanvas

緣由其實都是同樣的,跨域致使。後端

那有沒有什麼辦法能夠解決這個問題呢?

能夠試試 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 解析爲 anonymous

另外還有一點須要注意,那就是雖然沒有 crossOrigin 屬性,和設置 crossOrigin="use-credentials" 在默認狀況下都會報跨域出錯,可是性質上卻不同,二者有較大區別。

crossOrigin 兼容性

IE11+(IE Edge),Safari,Chrome,Firefox 瀏覽器均支持,IE9 和 IE10 會報 SecurityError 安全錯誤,以下截圖:

img

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 真實採坑記和建議

  1. 若是使用 vue 作數據渲染,不要在生成頁作太多數據處理的操做,提早把動態數據處理好,不然即使用 $nextTick 也會有在生成圖片時數據不完整的狀況
  2. 引用 CDN 上的圖片,須要設置 useCORS 爲 true,同時要保證全部圖片加載完成後再生成,可以使用 new Imaage 作預加載和判斷是否所有 load
  3. 用背景 background,生成的圖片清晰度不夠,會模糊;用 img 引入的方式可避免這個問題
  4. 在 iOS 系統的 13.4.1,沒法生成圖片,須要退回到 1.0.0-rc.4 版本,不要使用 1.0.0-rc.5 版本,issues 地址:https://github.com/niklasvh/html2canvas/issues/2205
  5. 可把生成的圖片設置透明度 opacity 爲 0,蓋在原有元素之上,便於在微信保存,不會由於生成的圖和原有元素略微有差距,而抖動。
相關文章
相關標籤/搜索