JavaScript: Clipboard API | 不用監聽鍵盤事件,還能複製非文字項目

YY
9 min readJun 25, 2021
Keyboard

要成為一名優秀的軟體工程師,首先要學會的必然是使用 ctrl/cmd + c, ctrl/cmd + v,然後悠遊在 StackOverflow

如今,透過游標選取特定字段,按下 ctrl/cmd + c,再透過 ctrl/cmd + v 以達成把字段輸入到指定位置,已成為人們再熟悉不過的日常。但如果想讓使用者在網頁上用最習慣的鍵盤按鍵方式複製非文字的項目該怎麼辦?

在還沒聽過 Clipboard API 前,最直覺的想法就是綁 keyboard event 來監聽使用者的組合鍵,再做對應的 handler。然而有了 Clipboard API 就能更方便來處理這些複製貼上的需求,也能實現更多有趣的功能

Clipboard API

直接切入主題,Clipboard API 就是能存取你電腦剪貼簿的小工具,在操作上有兩種使用方式:

透過 Clipboard API 去讀寫系統剪貼簿

navigator.clipboard.readText()navigator.clipboard.writeText('newClipText')

利用這種方式可以直接去取得剪貼簿的內容或塞入資料到剪貼簿中,要注意的是在讀取剪貼簿內容的時候需要取得使用者的授權 (Permission API),其他更多 method 及說明請參考 MDN

在使用者操作複製貼上動作時介入

而這種方法則是本篇文章想著重的部分,所謂操作時介入即為事件監聽,我們可以透過 cutcopypaste 三種 event listener 監聽使用者透過鍵盤或 contextmenu 操作剪下、複製、貼上的行為,再給予自定義的 handler 做對應的處理

document.addEventListener('copy', (e) => {
e.preventDefault()
e.clipboardData.setData(
'text',
document.getSelection().toString().toLocaleUpperCase()
)
})
copy event listener

上方 snippet 及 demo gif 即為透過監聽 copy 事件後將客製過的 string 塞進剪貼簿,當使用者再次以原生方式貼上時就會把這段文字貼出來

亦可透過監聽 paste 事件來取得使用者剪貼簿的內容:

document.addEventListener('paste', (e) => {
const text = e.clipboardData.getData('text')
})

DataTransfer

精明的你應該已經發現在上面的 snippet 中使用了 setDatagetData 來對剪貼簿 aka event.clipboardData 進行存取

這個 event.clipboardData 其實是個 DataTransfer 物件,如果有用過 Drag and Drop API 應該是已相當熟悉,因此我們能對 clipboardData 進行 setData & getData

存入剪貼簿

clipboardData.setData(format, data)

format 為自定義的 type 字串,或以 MIME type 來描述

data 則是所要存進 clipboard 的資料 (string)

讀取剪貼簿

clipboardData.getData(format)

取得 clipboard 中指定 format 的資料

DataTransfer.types

在 setData 時可以同時塞入多種 format 的資料進 clipboard

document.addEventListener('copy', (e) => {
e.preventDefault()
e.clipboardData.setData('aa', '123')
e.clipboardData.setData('bb', '456')
})

然後再透過 onPaste 時分別讀取

document.addEventListener('paste', (e) => {
console.log(e.clipboardData.types) // ['aa', 'bb']
const aa = e.clipboardData.getData('aa')
const bb = e.clipboardData.getData('bb')
})

像是如果在 vscode 中複製文字,可以發現他在系統剪貼簿中存了三種資料:

如此一來我們可以在監聽 paste 事件時判斷是否有 vscode-editor-data 的資料,再加以處理其 text/htmltext/plain 中的內容

用原生的方式複製非文字項目

在了解了如何介入使用者複製貼上的行為以及讀寫 clipboard 內的資料後,就能藉此實現讓使用者以原生 (contextmenu, cmd c/v) 的方式來複製貼上任何非文字的項目了

copy &paste non-text item
source code of above demo gif

如上所示,這邊以 React 做 demo 來實現複製貼上非文字的項目,接著來講解 App.js 中的細節

const defaultItems = {
a: { x: 14, y: 20, color: "red" },
b: { x: 140, y: 91, color: "yellow" },
c: { x: 401, y: 188, color: "blue" }
};
const [items, setItems] = useState(defaultItems);
const [selectedItem, setSelectedItem] = useState();

一開始先是定義了 defaultItems 裡面包含了三個 item 及其位置與顏色,接著在 component 內以 state 紀錄頁面中的 items 與被選到的 item aka selectedItem

const handleCopy = (e) => {
if (selectedItem) {
e.preventDefault();
e.clipboardData.setData(
"copy_item",
JSON.stringify(items[selectedItem])
);
}
};
document.addEventListener("copy", handleCopy);

在 useEffect 中監聽 copy 事件,如果是有選擇 item 的狀況下進行複製 (cmd + c) 就把這個 item 的資訊以自訂的 copy_item format 存進 clipboard 中

const handlePaste = (e) => {
if (e.clipboardData.types.includes("copy_item")) {
e.preventDefault();
const { x, y, color } = JSON.parse(
e.clipboardData.getData("copy_item")
);
setItems({
...items,
[Date.now().toString()]: {
color,
x: x + 20,
y: y + 20
}
});
}
};
document.addEventListener("paste", handlePaste);

在同一個 effect 中監聽 paste 事件,當使用者嘗試貼上 (cmd + v) 時且 clipboard 中存的資料包含 copy_item 時,就產生一個新的 item data 塞進 state 裡,如此一來就達成了複製任何元件的功能了

跨視窗、跨應用程式

比起監聽 hotkey,使用 Clipboard API 監聽原生複製貼上事件除了更加方便外,還能達到跨視窗甚至跨應用程式的複製貼上,因為是直接存取系統的剪貼簿

就像上面有看到的,當你在 vscode 中複製文字再到 chrome 裡貼上,就可以從 onPaste handler 裡發現有客製的內容,因此也能方便地做到在同個 web app 中跨視窗進行複製貼上,如下 gif

cross window copy & paste

注意事項

如果在使用 Clipboard API 過程中遇到困難,也許跟 SSL、permission、瀏覽器支援度有關係,像不同瀏覽器間若 DataTransfer 的 type 並非 MIME type 似乎就無法達成預期效果

--

--