Clipboard API - 你複製貼上了什麼?

alexianalexian
2023-03-19
JavaScript前端開發

前言

平常在使用一些內容編輯的應用如 Notion、Slack 時,只要全選內容,就可以輕鬆複製包含格式的內容到剪貼簿,或者在一些教學文件按一下 📋 圖示,就能把資料複製到剪貼簿;但是你有沒有疑惑過,你複製的網頁內容,為什麼會在不同的應用貼上時,產生了不一樣的結果呢?今天就來看看,你究竟複製貼上了什麼。

實地考察

要看到複製的內容是什麼,我們可以使用瀏覽器實作的 Clipboard API ,先打開 Chrome 的開發者工具,貼上以下程式碼,但先不要執行(避免你最後複製的內容,是這段程式碼 😂):

// 由於 navigator.clipboard 必須在網頁在 focus 的狀態,故延遲 3 秒讓你重新 focus 網頁
setTimeout(() => {
    navigator.clipboard.read().then(t => console.log(t))
}, 3000);

接下來我們試著在 Medium 的文章中使用複製包含連結/格式的內容,複製好後執行我們前一個步驟輸入的程式碼(會需要允許存取 clipboard-read 權限),執行後要再點擊網頁可以看到你在剪貼簿的內容:

複製內容的真面目 ClipboardItem

複製的內容會是一個包含 ClipboardItem 實例的陣列,而 ClipboardItem 實例又可以帶有多個媒體類別 (**MIME type**)的內容,如 text/plain 、text/html 、image/png 等。像前面範例所複製的內容,就包含 text/plain、text/html 兩個媒體類別,分別作用於不同的貼上情境。

例如在純文字編輯器內執行貼上,系統會使用 ClipboardItem 中 text/plain 格式的內容貼上;而一些郵件編輯器中,則會貼上 text/html 格式的內容,但最終貼上的內容還是要視乎應用程式的設計而定。

不同的應用程式,會貼上不同媒體類別的內容

使用 Clipboard API 動態寫入內容

純文字的內容

有讀取的方法,自然就有寫入的方法,先來看最簡單的 navigator.clipboard.writeText() 方法,只需要傳入要複製的字串內容即可(Codepen 範例):

$copyBtn.addEventListener('click', () => {
  navigator.clipboard.writeText('這是寫入剪貼簿的內容');
});

另外,navigator.clipboard.writeText() 會回傳 Promise ,所以可以實現複製成功後的事件處理 (Codepen 範例):

$copyBtn.addEventListener('click', async () => {
  await navigator.clipboard.writeText('這是寫入剪貼簿的內容!');
  
  $notice.textContent = '已複製!';
  setTimeout(() => $notice.textContent = '', 2000);
});

其他媒體類別的內容

相對於純文字的內容,要把其他媒體類別的內容放入剪貼簿相對較複雜,首先要使用的方法為navigator.clipboard.write(),該方法接受一個只有 ClipboardItem 項目的陣列(但直到目前為止各大瀏覽器的寫入剪貼簿實作 navigator.clipboard.write() ,只能帶入單個 ClipboardItem 至陣列中 — 來源),接受一個物件作為參數,屬性是對應檔案大一月金型只接受 Blob 實例,假如要塞入 HTML 內容,就要建立對應的 text/html 媒體類別的 Blob 實例 (Codepen 範例):

$copyBtn.addEventListener('click', async () => {
  const clipboardItem = new ClipboardItem({
    "text/html": new Blob(
      ['<h1>這裡是 HTML 內容</h1><p>想不到吧!</p>'],
      { type: "text/html" },
    ),
  });

  await navigator.clipboard.write([clipboardItem]);
});

和 navigator.clipboard.writeText() 一樣,navigator.clipboard.write() 會回傳 Promise;另外,如果貼上的區塊需要媒體類別不存在於剪貼簿中,會沒有內容可以貼上;想解決這個問題,可以讓 ClipboardItem 攜帶多個媒體類別的內容 (Codepen 範例):

$copyBtn.addEventListener('click', async () => {
  const clipboardItem = new ClipboardItem({
    "text/plain": new Blob(
			['純文字內容在這裡!'],
			{ type: "text/plain" },
		),
    "text/html": new Blob(
			['<h1>這裡是 HTML 內容</h1><p>想不到吧!</p>'],
			{ type: "text/html" },
    ),
  });

  await navigator.clipboard.write([clipboardItem]);
    
  $notice.textContent = '已複製!';
  setTimeout(() => $notice.textContent = '', 2000);
});

應該現在就使用嗎?

以往要在 JS 中實現複製行為,通常會使用 execCommand(’copy’),但由於安全性有疑慮(無法檢查使用者複製內容是否安全)、以及實用性不足(無法複製除 text/plain,text/html 以外的格式)的原因;而 Clipboard API 就是為了補足前者缺失而製訂的 API,除了可以放入圖像 image/png 、image/jpg 以外,更可以放入自訂的媒體類別 來源 ,取得更多樣的複製內容。

聽起來應該要全面使用它進行實作,但其實還是有些疑慮。

不太好的通用性

理想很豐滿,現實很骨感。雖然 W3C 制訂了 Clipboard API 的標準,但在瀏覽器權限上,firefox 走出了和其他瀏覽器不一樣的路:

  1. 預設不能使用 navigator.clipboard.read() (需要在 about:config 介面打開 dom.events.asyncClipboard.read 選項)
  2. 預設不能使用 navigator.clipboard.write() (需要在 about:config 介面打開 dom.events.asyncClipboard.write 選項),而且打開後,只能使用 DataTransfer 實例代替 ClipboardItem 放入內容
  3. 87 版後新增了 ClipboardItem 但預設不能使用(需要在 about:config 介面打開 dom.events.asyncClipboard.clipboardItem 選項)

firefox 上要開啟的權限

也因此導致 Clipboard API 的通用性不夠全面,需要為不同瀏覽器進行調整,也就影響了使用它的意願。

什麼開發情境適合使用呢?

Clipboard API 在 Chromium 上有較完整的實現,如果你使用的平台是 Chrome 例如在開發 Chrome extension,或是 Electron(Chromium Based)應用,那麼就可以大膽的使用它。

同場加映 - ClipboardEvent

Clipboard API 對使用 JS 動態存取剪貼簿非常實用,但如果使用者在網頁中使用系統原生的複製、貼上功能,則可以使用 copy 、paste 事件來進行監聽,例如有一些討論區會在你複製內容時,在結尾加入 來源於 xxx 的字句,這時就可以用 copy 事件:

document.addEventListener('copy', function(event) {
  const selectedText = window.getSelection().toString();
  event.clipboardData.setData('text/plain', `${selectedText} 來源於: Alex`);
  event.preventDefault();
});

若要根據在使用者執行貼上時對其內容進行處理,則可以用paste 事件:

document.addEventListener('paste', function(event) {
  const pastedText = event.clipboardData.getData('text/plain');
	if (pastedText.includes('Alex')) {
    console.error('這內容有毒!');
  }

  event.preventDefault();
});

總結

本文介紹了 Clipboard API,可以讓開發者動態存取剪貼簿內容,並支援多種媒體類別。然而各家瀏覽器對其支援程度有所差異,因此通用性還有待改善。此外還介紹了 copy 和 paste 事件,可以用來監聽使用者在網頁中使用系統原生的複製、貼上功能。

如果有錯漏或是不理解的地方,歡迎指正或留言一起討論喔!

參考來源

  • MDN - Clipboard API
  • MDN - ClipboardEvent
  • Google Developers: Async Clipboard API
  • Web custom formats for the Async Clipboard API

上一篇

從頭開始學習開發 Chrome extension (v3 版本)

下一篇

開發效率提升 - 操作鍵盤 & 快捷鍵篇


Alex Ian
Alex Ian

ReactJS, VueJS 畫畫/寫作/前端開發

Alex Ian
Alex Ian

ReactJS, VueJS 畫畫/寫作/前端開發


共29篇文章
文章分類
  • 前端開發14
  • JavaScript7
  • 那些前端開發應該要知道的小事5
  • Gatsby.js3
  • ChromeExtension2
  • NetlifyCMS2
  • 生產力工具2
  • 關於我1
  • 非Coding1
  • Svelte1
  • raycast1
  • toolRecommend1
  • productivity1
  • MutationObserver1
  • observer pattern1
  • 開發效率1
  • will-change1
  • GitHubCopilot1
  • 生產力1
  • ChatGPT1
  • AI1
  • Copilot1
  • WebAssembly1
  • SEO1
  • GoogleSearchConsole1
  • react1
  • applescript1
  • 閱讀1
  • css1
  • grid1
  • micro-frontend1

Build with GatsbyJS and React 18.2.0. Hosted on Netlify.

The original code is open source and available at calpa/gatsby-starter-calpa-blog

Copyright ©AlexIan's blog 2025.

  1. 從頭開始上架 Chrome extension

    上回提到我們在本地端建好一個開發版本的 Chrome …
  2. Gatsby 部落格更新記錄 1.0

    首先把 Blog 連結貼在最前面:https://alex-ian.me/ 許多工程師 …
  3. Debounce & Throttle - 那些前端開發應該要知道的小事(一)

    前言 也許一開始接觸前端開發的新手們,都有使 …
  4. 使用 GitHub Copilot 一個月心得

    後悔自己為何沒有早點課金 在 Modern Web Conference 2023 觀後感 …
  5. 什麼是 Headless CMS?以探索 Netlify CMS 為例

    小弟的部落格使用 Gatsby.js 框架來架設,作為一個靜 …
  6. Modern Web Conference 2023 觀後感

    https://modernweb.tw/ 前言 Modern Web Conference (MWC) 作為每年度經典的網頁開 …
  7. 什麼是語法糖(Syntactic sugar)? - 2024 你要知道的 JS 語法糖 🍭

    語法糖能吃嗎? 在算數學時,不知道你什麼時候開 …
  8. 閉包 - 那些前端開發應該要知道的小事(三)

    前言 開始這個系列的原因,是因為雖然在程式中 …
  9. Raycast — Mac 提升工作效率的啟動工具

    Mac 的使用者,應該對 Spotlight 這個系統預設的啟動工具 …
  10. 使用 Notion 管理我的年度目標 暨 2023 回顧 & 2024 展望

    都進入 2024 才來展望 想要成為流量的奴隸,自然要 …
  11. 慢半拍來初嚐 Svelte

    Svelte 都出來兩年多的框架現在才來玩,有夠慢半拍 …
  12. 從頭開始學習開發 Chrome extension (v3 版本)

    Chrome extension 經歷過 v1, v2 後,來到了 v3,雖然曾小量接觸 v2 …
  13. 留言簿

    歡迎留言
  14. addEventListener的第三個參數 - 那些前端開發應該要知道的小事(二)

    前言 開始這個系列的原因,是因為雖然在程式中 …
  15. 在中大型企業下微前端的實現與挑戰

    我們先聊工程面,人的部分先不要 小弟任職前端 …
  16. will-change — 那些前端開發應該要知道的小事(七)

    你要做動畫你要先說啊 前端開發少不免會需要 …
  17. 關於我

    關於我 我是Alex Ian,是個小小的前端工程師,出來工 …
  18. 開發效率提升 - 操作鍵盤 & 快捷鍵篇

    我國中的電腦課,會教導學生倉頡和速成輸入法 …
  19. macbook-pro(retina) late2012 換電池記錄

    這篇文章其實與Coding無關,主要是記錄mbp換電池的 …
  20. polyfill是什麼?

    人在江湖,身不由己,在接案的過程中,總有一些你 …
所有結果