正在解除封鎖剪貼簿

更安全地存取剪貼簿的文字和圖片

傳統的存取系統剪貼簿的方法是透過 document.execCommand() 以用於剪貼簿互動雖然這項功能受到廣泛支援,但這種剪貼方式的成本很高:剪貼簿存取是同步的,只能讀取及寫入 DOM。

這對於小段文字來說沒什麼問題,但在許多情況下,阻擋剪貼簿轉移頁面會導致使用者體驗不佳。進行耗時的清潔或 可能需先解碼圖片,才能安全地貼上內容。瀏覽器 可能就需要從貼上的文件中載入或內嵌連結的資源。這麼做 封鎖頁面,同時等待磁碟或網路。想像新增權限 必須在要求時封鎖網頁 。同時,這些權限 剪貼簿互動的 document.execCommand() 定義鬆散且變動 能在不同瀏覽器間切換

非同步剪貼簿 API 為解決這些問題,提供定義明確且不會 封鎖網頁。Async Clipboard API 僅限於在大多數瀏覽器上處理文字和圖片,但支援程度各有不同。請務必仔細研究下列各節的瀏覽器相容性總覽。

複製:將資料寫入剪貼簿

writeText()

如要將文字複製到剪貼簿,請呼叫 writeText()。由於這個 API 非同步,writeText() 函式會傳回可解析或可解析的 Promise 拒絕 (取決於傳送的文字是否成功複製):

async function copyPageUrl() {
  try {
    await navigator.clipboard.writeText(location.href);
    console.log('Page URL copied to clipboard');
  } catch (err) {
    console.error('Failed to copy: ', err);
  }
}

瀏覽器支援

  • Chrome:66。
  • Edge:79。
  • Firefox:63。
  • Safari:13.1.

資料來源

write()

事實上,writeText() 只是一般 write() 方法的便利方法,您也可以使用這個方法將圖片複製到剪貼簿。喜歡「writeText()」,它 並傳回 Promise

如要將圖片寫入剪貼簿,您需要將圖片設為 blob。這麼做的方法之一,是使用 fetch() 向伺服器要求圖片,然後在回應中呼叫 blob()

基於各種原因,從伺服器要求圖片可能不切實際或無法執行。幸好,只要將圖片繪製到畫布上 呼叫畫布」 toBlob() 方法。

接著,將 ClipboardItem 物件的陣列做為參數傳遞至 write() 方法。目前您只能一次傳送一張圖片,但我們希望日後能支援多張圖片。ClipboardItem 採用的 圖片的 MIME 類型做為鍵,並將 blob 做為值。適用於 blob 從 fetch()canvas.toBlob() (blob.type 屬性) 取得的物件 自動包含正確的圖片 MIME 類型。

try {
  const imgURL = '/images/generic/file.png';
  const data = await fetch(imgURL);
  const blob = await data.blob();
  await navigator.clipboard.write([
    new ClipboardItem({
      // The key is determined dynamically based on the blob's type.
      [blob.type]: blob
    })
  ]);
  console.log('Image copied.');
} catch (err) {
  console.error(err.name, err.message);
}

或者,您也可以將承諾寫入 ClipboardItem 物件。透過這個模式,您必須事先知道資料的 MIME 類型。

try {
  const imgURL = '/images/generic/file.png';
  await navigator.clipboard.write([
    new ClipboardItem({
      // Set the key beforehand and write a promise as the value.
      'image/png': fetch(imgURL).then(response => response.blob()),
    })
  ]);
  console.log('Image copied.');
} catch (err) {
  console.error(err.name, err.message);
}

瀏覽器支援

  • Chrome:76.
  • Edge:79。
  • Firefox:127。
  • Safari:13.1.

資料來源

複製事件

在使用者啟動剪貼簿副本時 且「不會」呼叫 preventDefault()copy事件 包含 clipboardData 屬性,其中包含格式正確的項目。 如要實作自己的邏輯,必須呼叫 preventDefault() 以 防止預設行為改用您的實作。 在這種情況下,clipboardData 會是空白。請考慮含有文字和圖片的網頁,當使用者選取所有內容並啟動剪貼簿複製作業時,自訂解決方案應捨棄文字,只複製圖片。您可以按照下列程式碼範例執行這項操作。本例未涵蓋的內容是,當系統不支援 Clipboard API 時,如何改用舊版 API。

<!-- The image we want on the clipboard. -->
<img src="kitten.webp" alt="Cute kitten.">
<!-- Some text we're not interested in. -->
<p>Lorem ipsum</p>
document.addEventListener("copy", async (e) => {
  // Prevent the default behavior.
  e.preventDefault();
  try {
    // Prepare an array for the clipboard items.
    let clipboardItems = [];
    // Assume `blob` is the blob representation of `kitten.webp`.
    clipboardItems.push(
      new ClipboardItem({
        [blob.type]: blob,
      })
    );
    await navigator.clipboard.write(clipboardItems);
    console.log("Image copied, text ignored.");
  } catch (err) {
    console.error(err.name, err.message);
  }
});

如果是 copy 事件:

瀏覽器支援

  • Chrome:1.
  • 邊緣:12。
  • Firefox:22。
  • Safari:3.

資料來源

針對 ClipboardItem

瀏覽器支援

  • Chrome:76.
  • Edge:79,
  • Firefox:127。
  • Safari:13.1。

資料來源

貼上:讀取剪貼簿中的資料

readText()

如要讀取剪貼簿中的文字,請呼叫 navigator.clipboard.readText(),並等待傳回的承諾解析:

async function getClipboardContents() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('Pasted content: ', text);
  } catch (err) {
    console.error('Failed to read clipboard contents: ', err);
  }
}

瀏覽器支援

  • Chrome:66。
  • Edge:79。
  • Firefox:125。
  • Safari:13.1.

資料來源

read()

navigator.clipboard.read() 方法也為非同步性質,且會傳回 承諾。如要從剪貼簿讀取圖片,請取得 ClipboardItem 物件的清單,然後對其進行疊代。

每個 ClipboardItem 都可以保存其類型的內容,因此您必須 使用 for...of 迴圈疊代類型清單。針對每個類型,請使用目前類型做為引數呼叫 getType() 方法,以取得相應的 blob。如同先前,這個程式碼不會與圖片綁定,日後也會支援其他檔案類型。

async function getClipboardContents() {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      for (const type of clipboardItem.types) {
        const blob = await clipboardItem.getType(type);
        console.log(URL.createObjectURL(blob));
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
}

瀏覽器支援

  • Chrome:76.
  • Edge:79,
  • Firefox:127。
  • Safari:13.1。

資料來源

使用貼上檔案

使用者可以使用剪貼簿鍵盤快速鍵,例如 ctrl + cctrl + v。Chromium 會在剪貼簿上公開唯讀檔案,如下所述。當使用者按下作業系統的預設貼上捷徑,或在瀏覽器的選單列中依序點選「編輯」和「貼上」時,就會觸發這項事件。不需要額外的管線程式碼。

document.addEventListener("paste", async e => {
  e.preventDefault();
  if (!e.clipboardData.files.length) {
    return;
  }
  const file = e.clipboardData.files[0];
  // Read the file's contents, assuming it's a text file.
  // There is no way to write back to it.
  console.log(await file.text());
});

瀏覽器支援

  • Chrome:3.
  • Edge:12.
  • Firefox:3.6。
  • Safari:4.

資料來源

貼上事件

如前所述,我們計劃推出適用於 Clipboard API 的事件, 但現在您可以使用現有的 paste 事件。它與用於讀取剪貼簿文字的新非同步方法搭配使用時,運作良好。如同 copy 事件,別忘了呼叫 preventDefault()

document.addEventListener('paste', async (e) => {
  e.preventDefault();
  const text = await navigator.clipboard.readText();
  console.log('Pasted text: ', text);
});

瀏覽器支援

  • Chrome:1.
  • Edge:12.
  • Firefox:22。
  • Safari:3.

資料來源

處理多個 MIME 類型

多數導入方式會將多種資料格式寫入剪貼簿,以便單一剪下 或複製作業這有兩個原因:首先,您無法得知使用者要將文字或圖片複製到哪個應用程式,其次,許多應用程式都支援以純文字貼上結構化資料。這通常 向使用者顯示的編輯選單項目,名稱包括貼上與 比對樣式貼上 (不套用格式)

以下範例說明如何進行這項操作。此範例使用 fetch() 來取得 但也可能是來自 <canvas>File System Access API

async function copy() {
  const image = await fetch('kitten.png').then(response => response.blob());
  const text = new Blob(['Cute sleeping kitten'], {type: 'text/plain'});
  const item = new ClipboardItem({
    'text/plain': text,
    'image/png': image
  });
  await navigator.clipboard.write([item]);
}

安全性和權限

剪貼簿存取向來對瀏覽器而言都是安全疑慮。如果沒有適當的權限,網頁可能會悄悄將所有類型的惡意內容複製到使用者的剪貼簿,導致貼上時產生災難性的結果。想像一下,如果網頁會悄悄將 rm -rf /解壓縮炸彈圖片複製到剪貼簿,會發生什麼事?

瀏覽器提示,要求使用者授予剪貼簿權限。
Clipboard API 的權限提示。

讓網頁無限制地讀取剪貼簿內容更是麻煩。使用者會定期複製密碼等機密資訊 傳送至剪貼簿,方便任何沒有 對使用者的告知

如同許多新的 API,剪貼簿 API 只支援在 HTTPS為防範濫用行為,請務必允許網頁存取剪貼簿 查看使用中的分頁處於活動分頁中的網頁可以寫入剪貼簿,而無需要求權限,但讀取剪貼簿內容一律需要權限。

複製和貼上的權限已新增至 Permissions API。系統會自動授予頁面 clipboard-write 權限 查看使用中的分頁必須要求「clipboard-read」權限,您可以 嘗試讀取剪貼簿中的資料以下程式碼顯示後者:

const queryOpts = { name: 'clipboard-read', allowWithoutGesture: false };
const permissionStatus = await navigator.permissions.query(queryOpts);
// Will be 'granted', 'denied' or 'prompt':
console.log(permissionStatus.state);

// Listen for changes to the permission state
permissionStatus.onchange = () => {
  console.log(permissionStatus.state);
};

您也可以控管是否需要透過使用者手勢叫用 請使用 allowWithoutGesture 選項貼上。這個值的預設值因瀏覽器而異,因此您應一律加入這個值。

以下是 Clipboard API 非同步特性的實用之處: 嘗試讀取或寫入剪貼簿資料時,系統會自動提示使用者 如未授予這項權限。由於 API 是基於承諾,因此這項作業完全透明,使用者拒絕剪貼簿權限會導致承諾遭到拒絕,讓網頁可以適當回應。

因為瀏覽器只有在使用中的分頁處於使用中狀態時,才允許存取剪貼簿。 如果直接將程式碼貼入 因為開發人員工具本身就是使用中的分頁,所以需要開啟瀏覽器控制台有秘訣:延遲 使用 setTimeout() 存取剪貼簿,然後在網頁中快速按一下,即可 聚焦在呼叫函式前的重點:

setTimeout(async () => {
  const text = await navigator.clipboard.readText();
  console.log(text);
}, 2000);

權限政策整合

如要在 iframe 中使用 API,您必須透過權限政策啟用 API,該政策定義了一種機制,可讓您有選擇地啟用及停用各種瀏覽器功能和 API。具體來說,您需要傳遞 clipboard-readclipboard-write (視應用程式需求而定)。

<iframe
    src="index.html"
    allow="clipboard-read; clipboard-write"
>
</iframe>

特徵偵測

如要使用 Async 剪貼簿 API 並支援所有瀏覽器,請測試 navigator.clipboard,並改用舊版方法。舉例說明 可以執行貼上來納入其他瀏覽器

document.addEventListener('paste', async (e) => {
  e.preventDefault();
  let text;
  if (navigator.clipboard) {
    text = await navigator.clipboard.readText();
  }
  else {
    text = e.clipboardData.getData('text/plain');
  }
  console.log('Got pasted text: ', text);
});

事實並非如此在 Async 剪貼簿 API 推出之前,各個網路瀏覽器都會混用不同的複製和貼上實作方式。在大部分的瀏覽器中 就能透過單一路徑 《document.execCommand('copy')》和《document.execCommand('paste')》。如果要複製的文字是 DOM 中不存在的字串,則必須將其插入 DOM 並選取:

button.addEventListener('click', (e) => {
  const input = document.createElement('input');
  input.style.display = 'none';
  document.body.appendChild(input);
  input.value = text;
  input.focus();
  input.select();
  const result = document.execCommand('copy');
  if (result === 'unsuccessful') {
    console.error('Failed to copy text.');
  }
  input.remove();
});

示範

您可以在下方的示範中試用 Async Clipboard API。您可以在 Glitch 上重新混合文字示範圖片示範,以便進行實驗。

第一個範例示範如何將文字移入及移出剪貼簿。

如要試用含有圖片的 API,請使用此示範。請注意,系統僅支援 PNG 檔案,且僅適用於少數瀏覽器

特別銘謝

非同步剪貼簿 API 是由 Darwin 實作 HuangGary Kačmarčík。Darwin 也提供了示範。非常感謝Kyarik。再次感謝 Gary Kačmarčík 致敬 深入探討本文的某些部分

主頁橫幅由 Markus Winkler 提供 Unsplash