我們有時候會在一些網站看到「複製到剪貼簿」功能,它不但可以複製我們肉眼看見的文字,還能複製完整的格式讓你可以貼到 Teams 或 Word 之中,有時還能複製完全客製化的內容,其實這背後都是透過瀏覽器內建的 Clipboard API 達成的。今天這篇文章我就來梳理一下 Clipboard API 的一些用法與地雷。
常見的用法
我們在 Azure DevOps Service 的 Azure Boards 查看 Work Item (工作項目) 時,就有個相當隱密的功能,可以幫助你複製豐富格式的 HTML 到剪貼簿,方便你在 Teams 或其他支援豐富格式的編輯器中貼上(例如 Word、Outlook、Gmail、... 等等)。使用方式大概就是:
- 先開啟任意 Work Item
- 滑鼠移到標題的文字輸入框上
- 此時在文字輸入框最右邊會出現一個「複製工作項目標題」的按鈕
- 按下去之後就會複製一個擁有 HTML 格式的版本到剪貼簿中!
注意: 複製到剪貼簿功能也經常設計成鍵盤快速鍵的形式,如上圖就可以看到用 Shift+Alt+C
組合鍵也可以快速複製標題。
若是貼上到文字編輯器,內容會長這樣:
Product Backlog Item 42100: 轉換/轉申購
若是貼上到 Teams 或 Outlook 郵件中,內容會長這樣:
還有另一種就是在 GitHub 常會使用到的「複製程式碼到剪貼簿」功能,如下圖示,不過這裡就沒有連同複製豐富格式了,只有「純文字」的程式碼而已:
上述範例主要是「寫入剪貼簿」的用法,還有另一種是「讀取剪貼簿」的用法,而且還可以不僅僅讀取文字而已,連剪貼簿中的「圖片」也可以讀到,不過,使用者在第一次使用該功能時,會需要使用者明確授權才能用,所以其實比較少看到有網站實作這個功能,而且並不是所有瀏覽器都有支援「讀取剪貼簿」功能!
注意:Azure DevOps Service 的 Azure Boards 有支援在 Work Item 的欄位中按下 Ctrl-V
貼上使用者剪貼簿中的圖片,不過這個功能並不是透過 Clipboard API 實現的,而是透過 DOM 的 paste
事件實現的(ClipboardEvent),這部分不在本文討論範圍,但你可以輕易的透過 ChatGPT 找到範例程式,咒語是: Please act as a Senior Frontend developer. I want to paste image or text from user's clipboard to the web page using paste event. How can I deal with these functions?,中文的咒語也可以: 請扮演一位資深的前端開發人員,我想在網頁上透過 paste 事件讀取使用者的剪貼簿資料,並貼上圖片或文字到網頁中,我該如何透過 JavaScript 完成這些功能?能否給我一些 JavaScript 寫的範例程式。
如何從剪貼簿中讀取資料
你可以透過 navigator.clipboard.readText()
直接讀取目前使用者剪貼簿中的「文字」內容,也可以透過 navigator.clipboard.read()
讀取使用者剪貼簿中的「任意格式」內容,包含圖片都可以。
注意: 目前各瀏覽器對 Asynchronous Clipboard API 的支援度,只有 Firefox 完全不支援「讀取」功能,應該是「安全」考量。除此之外,在手機上的 Safari on iOS 與 Android Browser 也都不支援「讀取」功能!🔥
我們在 MDN 的 Clipboard.read() API 文件中也可以看到 Clipboard.read() - reading_image_data - code sample 範例,各位可以體驗一下「從剪貼簿讀取圖片」的感覺。
不過,使用者在你的網站第一次使用「讀取剪貼簿」功能時,會自動跳出一個授權畫面 (如下圖示),使用者必須同意該網站透過 JS 讀取剪貼簿,程式才能正常執行!
以下是一些範例程式:
-
讀取剪貼簿之前先判斷使用者是否有授權
const permission = await navigator.permissions.query({
name: "clipboard-read",
});
if (permission.state === "denied") {
throw new Error("Not allowed to read clipboard.");
}
注意: 在 iframe 中的網頁,預設「不允許」讀取剪貼簿內容,連跳出提示都不會喔!🔥
-
讀取「文字」資料
navigator.clipboard
.readText()
.then((clipText) => console.log(clipText));
-
讀取「圖片」資料
const clipboardContents = await navigator.clipboard.read();
for (const item of clipboardContents) {
if (!item.types.includes("image/png")) {
throw new Error("Clipboard contains non-image data.");
}
const blob = await item.getType("image/png");
destinationImage.src = URL.createObjectURL(blob);
}
完整範例請參考 MDN 的 Clipboard.read() 文件中的範例。
如何寫入資料到剪貼簿中
你可以透過 navigator.clipboard.writeText()
直接寫入「文字」到目前使用者的剪貼簿中,也可以透過 navigator.clipboard.write()
寫入「任意格式內容」到使用者的剪貼簿中,包含圖片都可以。
注意: Firefox 僅支援 navigator.clipboard.writeText()
寫入「文字」,不支援其他格式寫入,我想應該也是「安全」考量。
就瀏覽器的執行權限來說,只要是使用者正在使用的瀏覽器頁籤,該頁籤上的顯示的網頁預設都可以直接寫入剪貼簿,不需要使用者額外授權就能寫入!所以,一般來說剪貼簿功能都會設計成需要與使用者互動,例如準備一個按鈕讓使用者按下就能複製到剪貼簿,或是透過鍵盤快速鍵來複製等等,不太會設計成頁面載入時就自動寫入使用者的剪貼簿!(因為載入時頁面可能該頁面不在使用者目前的瀏覽器頁籤上)
以下是一些範例程式:
-
寫入「文字」到使用者的剪貼簿
以下範例寫入 Hello World
這個字串到使用者剪貼簿中:
navigator.clipboard.writeText('Hello World').then(
() => {
console.log('clipboard successfully set')
},
() => {
console.log('clipboard write failed');
}
);
注意: 上述這段程式碼預設無法在 F12 DevTool 中執行,因為你只要一開啟 F12 DevTool 就會離開頁面焦點,導致權限不足,無法使用「寫入剪貼簿」功能。
-
寫入「文字」到使用者的剪貼簿 (使用 navigator.clipboard.write()
API)
function setClipboard(text) {
const type = "text/plain";
const blob = new Blob([text], { type });
const data = [new ClipboardItem({ [type]: blob })];
navigator.clipboard.write(data).then(
() => {
/* success */
},
() => {
/* failure */
}
);
}
-
寫入 HTML 格式到使用者的剪貼簿
try {
const content = document.getElementsByClassName('js-output')[0].innerHTML;
const blobInput = new Blob([content], {type: 'text/html'});
const clipboardItemInput = new ClipboardItem({'text/html' : blobInput});
navigator.clipboard.write([clipboardItemInput]);
} catch(e) {
// Handle error with user feedback - "Copy failed!" kind of thing
console.log(e);
}
參考自 Copy rich HTML with the native Clipboard API
-
複製 Canvas 圖片到使用者的剪貼簿中
function copyCanvasContentsToClipboard(canvas, onDone, onError) {
canvas.toBlob((blob) => {
let data = [new ClipboardItem({ [blob.type]: blob })];
navigator.clipboard.write(data).then(
() => {
onDone();
},
(err) => {
onError(err);
}
);
});
}
-
複製 <img>
圖片到使用者的剪貼簿中
async function copyImageToClipboard() {
const imageUrl = document.querySelector('img').src;
const response = await fetch(imageUrl);
const imageData = await response.blob();
const clipboardItem = new ClipboardItem({
'image/png': imageData
});
try {
await navigator.clipboard.write([clipboardItem]);
console.log('Image copied to clipboard!');
} catch (error) {
console.error('Failed to copy image: ', error);
}
}
無法在 F12 DevTool 中執行 Clipboard API 的地雷
我還記得我第一次在玩 Clipboard API 的時候,一直在 F12 DevTool 中鬼打牆很久,因為怎樣都無法讀/寫剪貼簿資料,就是一定要寫到一份網頁中,不能直接在 F12 DevTool 測試這些 API,錯誤訊息如下:
Uncaught (in promise) DOMException: Document is not focused.
解決方法有點深奧,你必須依照以下步驟進行設定才行:
- 按下
F12
開啟 DevTool 視窗
- 按下
Escape
鍵開啟主控台導覽匣
- 切換到算繪(
Rendering
)頁籤
- 勾選模擬已聚焦的網頁(
Emulate a focused page
)
設定完成後,你就可以直接在「主控台」直接測試任何 Clipboard API 的效果了!👍
相關連結