允許跨域使用影像和畫布

HTML 提供了一個用於影像的 crossorigin 屬性,該屬性與相應的 CORS 標頭結合使用,允許由 <img> 元素定義的來自外部來源的影像在 <canvas> 中使用,就好像它們是從當前來源載入的一樣。

有關 crossorigin 屬性的使用方式的詳細資訊,請參閱 CORS 設定屬性

安全和汙染的畫布

由於畫布點陣圖中的畫素可以來自各種來源,包括從其他主機檢索的影像或影片,因此不可避免地會出現安全問題。

一旦您在畫布中繪製任何未經 CORS 批准且從其他來源載入的資料,該畫布就會變成汙染的。汙染的畫布是指不再被認為安全的畫布,任何嘗試從畫布中檢索影像資料的操作都會導致丟擲異常。

如果外部內容的來源是 HTML <img> 或 SVG <svg> 元素,則不允許嘗試檢索畫布的內容。

如果外部內容來自從 HTMLCanvasElementImageBitMap 獲取的影像,並且影像源不符合同源策略,則會阻止嘗試讀取畫布的內容。

在汙染的畫布上呼叫以下任何一項都會導致錯誤

當畫布被汙染時嘗試執行任何這些操作都會導致丟擲 SecurityError。這可以保護使用者免受透過使用影像在未經許可的情況下從遠端網站提取資訊的私有資料洩露。

儲存來自外部來源的影像

在此示例中,我們希望允許檢索來自外部來源的影像並將其儲存到本地儲存中。實現此目的需要配置伺服器以及為網站本身編寫程式碼。

Web 伺服器配置

首先,我們需要一個配置為託管具有 Access-Control-Allow-Origin 標頭的影像的伺服器,該標頭配置為允許跨域訪問影像檔案。

假設我們使用 Apache 提供我們的站點。請考慮以下 HTML5 Boilerplate 用於 CORS 影像的 Apache 伺服器配置檔案,如下所示

xml
<IfModule mod_setenvif.c>
  <IfModule mod_headers.c>
    <FilesMatch "\.(avifs?|bmp|cur|gif|ico|jpe?g|jxl|a?png|svgz?|webp)$">
      SetEnvIf Origin ":" IS_CORS
      Header set Access-Control-Allow-Origin "*" env=IS_CORS
    </FilesMatch>
  </IfModule>
</IfModule>

簡而言之,此配置使伺服器允許跨域從網際網路上的任何位置訪問圖形檔案(具有“.bmp”、“.cur”、“.gif”、“.ico”、“.jpg”、“.jpeg”、“.png”、“.svg”、“.svgz”和“.webp”副檔名的檔案)。

實現儲存功能

現在伺服器已配置為允許跨域檢索影像,我們可以編寫允許使用者將其儲存到 本地儲存 的程式碼,就像它們是從與程式碼執行相同的域提供服務一樣。

關鍵是使用 crossorigin 屬性,透過設定 crossOrigin 在將載入影像的 HTMLImageElement 中。這告訴瀏覽器在下載影像資料時請求跨域訪問。

開始下載

啟動下載的程式碼(例如,當用戶單擊“下載”按鈕時)如下所示

js
function startDownload() {
  let imageURL =
    "https://cdn.glitch.com/4c9ebeb9-8b9a-4adc-ad0a-238d9ae00bb5%2Fmdn_logo-only_color.svg?1535749917189";
  let imageDescription = "The Mozilla logo";

  downloadedImg = new Image();
  downloadedImg.crossOrigin = "anonymous";
  downloadedImg.addEventListener("load", imageReceived, false);
  downloadedImg.alt = imageDescription;
  downloadedImg.src = imageURL;
}

我們在這裡使用硬編碼的 URL(imageURL)和關聯的描述性文字(imageDescription),但這很容易來自任何地方。要開始下載影像,我們使用 Image() 建構函式建立一個新的 HTMLImageElement 物件。然後,透過將 crossOrigin 屬性設定為 "anonymous"(即允許跨域下載未經身份驗證的影像)來配置影像以允許跨域下載。為影像上觸發的 load 事件添加了一個事件偵聽器,這意味著影像資料已接收。向影像新增備用文字;雖然 <canvas> 不支援 alt 屬性,但該值可用於設定 aria-label 或畫布的內部內容。

最後,將影像的 src 屬性設定為要下載的影像的 URL;這將觸發下載開始。

接收和儲存影像

處理新下載的影像的程式碼位於 imageReceived() 方法中

js
function imageReceived() {
  const canvas = document.createElement("canvas");
  const context = canvas.getContext("2d");

  canvas.width = downloadedImg.width;
  canvas.height = downloadedImg.height;
  canvas.innerText = downloadedImg.alt;

  context.drawImage(downloadedImg, 0, 0);
  imageBox.appendChild(canvas);

  try {
    localStorage.setItem("saved-image-example", canvas.toDataURL("image/png"));
  } catch (err) {
    console.error(`Error: ${err}`);
  }
}

imageReceived() 函式用於處理接收下載影像的 HTMLImageElement 上的 "load" 事件。一旦所有下載資料都可用,就會觸發此事件。它首先建立一個新的 <canvas> 元素,我們將使用它將影像轉換為資料 URL,並透過變數 context 獲取畫布的 2D 繪圖上下文 (CanvasRenderingContext2D)。

畫布的大小調整為與接收到的影像匹配,內部文字設定為影像描述,然後使用 drawImage() 將影像繪製到畫布上。然後將畫布插入文件中,以便影像可見。

現在是時候將影像實際儲存到本地了。為此,我們使用 Web 儲存 API 的本地儲存機制,該機制透過全域性 localStorage 進行訪問。畫布方法 toDataURL() 用於將影像轉換為表示 PNG 影像的 data:// URL,然後使用 setItem() 將其儲存到本地儲存中。

另請參閱