正在解除封鎖剪貼簿

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

傳統的存取系統剪貼簿的方法是透過 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);
  }
}

瀏覽器支援

  • 66
  • 79
  • 63
  • 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);
}

瀏覽器支援

  • 66
  • 79
  • 127
  • 13.1

資料來源

複製事件

在使用者啟動剪貼簿副本時 且「不會」呼叫 preventDefault()copy事件 包含 clipboardData 屬性,其中包含格式正確的項目。 如要實作自己的邏輯,必須呼叫 preventDefault() 以 防止預設行為改用您的實作。 在這種情況下,clipboardData 會是空的。 假設某個網頁含有文字和圖片,當使用者選取所有項目並 啟動剪貼簿複本,您的自訂解決方案應只捨棄文字,且僅 然後複製圖片具體做法如以下程式碼範例所示。 這個範例並未涵蓋的內容 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 事件:

瀏覽器支援

  • 1
  • 12
  • 22
  • 3

資料來源

針對 ClipboardItem

瀏覽器支援

  • 76
  • 79
  • 127
  • 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);
  }
}

瀏覽器支援

  • 66
  • 79
  • 125
  • 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);
  }
}

瀏覽器支援

  • 66
  • 79
  • 127
  • 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());
});

瀏覽器支援

  • 3
  • 12
  • 3.6
  • 4

資料來源

貼上事件

如前所述,我們計劃推出適用於 Clipboard API 的事件, 但現在您可以使用現有的 paste 事件。與新的應用程式 讀取剪貼簿文字的非同步方法與 copy 事件一樣,請避免 忘記呼叫 preventDefault()

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

瀏覽器支援

  • 1
  • 12
  • 22
  • 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。具體來說,您需要傳遞 clipboard-readclipboard-write (視應用程式需求而定)。

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

特徵偵測

如要使用 Async Clipboard 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 Clipboard 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