在過去的幾年裡我們只能使用 document.execCommand
來操作剪貼簿。不過,這種操作剪貼簿的操作是同步的,並且只能讀取和寫入 DOM。
現在 Chrome 66 已經支援了新的 Async Clipboard API,作為 execCommand
替代品。
這個新的 Async Clipboard API 還可以使用 Promise 來簡化剪貼簿事件並將它們與 Drag-&-Drop API 一起使用。
演示視訊:zhuanlan.zhihu.com/p/34698155
複製:將文字寫入剪貼簿
writeText()
可以把文字寫入剪下板。writeText()
是非同步的,它返回一個 Promise:
navigator.clipboard.writeText('要複製的文字')
.then(() => {
console.log('文字已經成功複製到剪下板');
})
.catch(err => {
// This can happen if the user denies clipboard permissions:
// 如果使用者沒有授權,則丟擲異常
console.error('無法複製此文字:', err);
});
複製程式碼
還可以使用非同步函式 的 async
和 await
:
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);
}
}
複製程式碼
貼上:從剪貼簿中讀取文字
和複製一樣,可以通過呼叫 readText()
從剪貼簿中讀取文字,該函式也返回一個 Promise:
navigator.clipboard.readText()
.then(text => {
console.log('Pasted content: ', text);
})
.catch(err => {
console.error('Failed to read clipboard contents: ', err);
});
複製程式碼
為了保持一致性,下面是等效的非同步函式:
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);
}
}
複製程式碼
處理貼上事件
有計劃推出檢測剪貼簿更改的新事件,但現在最好使用“貼上”事件。它很適合用於閱讀剪貼簿文字的新非同步方法:
document.addEventListener('paste', event => {
event.preventDefault();
navigator.clipboard.readText().then(text => {
console.log('Pasted text: ', text);
});
});
複製程式碼
安全和許可權
剪貼簿訪問一直為瀏覽器帶來安全問題。如果沒有適當的許可權,頁面可能會悄悄地將所有惡意內容複製到使用者的剪貼簿,貼上時會產生災難性的結果。想象一下,一個網頁,靜靜地複製 rm -rf /
或解壓縮炸彈影像到剪貼簿。
讓網頁不受限制地讀取剪貼簿更加麻煩。使用者經常將敏感資訊(如密碼和個人詳細資訊)複製到剪貼簿,然後可以通過任何頁面閱讀,而使用者根本無法察覺。
與許多新的 API 一樣,navigator.clipboard
僅支援通過 HTTPS 提供的頁面。為了防止濫用,只有當頁面處於活動選項卡時才允許剪貼簿訪問。活動選項卡中的頁面可以在不請求許可權的情況下寫入剪貼簿,但從剪貼簿中讀取始終需要許可權。
為了更容易,複製和貼上的兩個新許可權已新增到 Permissions API 中。當頁面處於活動選項卡時,clipboard-write 許可權會自動授予頁面。當您通過從剪貼簿中讀取資料時,則必須要求獲取 clipboard-read 許可權。
{ name: 'clipboard-read' }
{ name: 'clipboard-write' }
複製程式碼
與使用許可權 API 的任何其它內容一樣,可以檢查您的應用是否具有與剪貼簿互動的許可權:
navigator.permissions.query({
name: 'clipboard-read'
}).then(permissionStatus => {
// permissionStatus.state 的值是 'granted'、'denied'、'prompt':
console.log(permissionStatus.state);
// 監聽許可權狀態改變事件
permissionStatus.onchange = () => {
console.log(permissionStatus.state);
};
});
複製程式碼
以下是剪貼簿 API 的“非同步”部分真正派上用場的地方:嘗試讀取或寫入剪貼簿資料將自動提示使用者獲得許可權(如果尚未授予)。由於 API 是基於 Promise 的,所以如果使用者拒絕剪貼簿許可權時,Promise 將被 reject,因此頁面可以適當地作出響應。
因為只有當頁面是當前活動選項卡時,Chrome 才允許剪貼簿訪問,因此如果直接貼上到 DevTools 中,則會發現這裡的一些示例執行不正確,因為此時 DevTools 本身是活動選項卡(頁面不是活動選項卡)。有一個技巧:我們需要使用 setTimeout 推遲剪貼簿訪問,然後在呼叫函式之前快速單擊頁面內部以使頁面獲取焦點:
setTimeout(async () => {
const text = await navigator.clipboard.readText();
console.log(text);
}, 2000);
複製程式碼
回顧
在引入非同步剪貼簿 API 之前,我們在 Web 瀏覽器中混合了不同的複製和貼上實現。
在大多數瀏覽器中,可以使用 document.execCommand('copy')
和觸發瀏覽器自己的複製和貼上 document.execCommand('paste')
。如果要複製的文字是不存在於 DOM 中的字串,我們必須將其插入到 DOM 中並選擇它:
button.addEventListener('click', e => {
const input = document.createElement('input');
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.');
}
})
複製程式碼
同樣,以下是您如何在不支援新的 Async Clipboard API 的瀏覽器中處理貼上的內容:
document.addEventListener('paste', e => {
const text = e.clipboardData.getData('text/plain');
console.log('Got pasted text: ', text);
})
複製程式碼
在 Internet Explorer 中,我們也可以通過 window.clipboardData
訪問剪貼簿。如果在使用者手勢內進行訪問(例如點選事件) - 以負責任的方式請求許可權的一部分 - 則不顯示許可權提示。
檢測和回退
在支援所有瀏覽器的同時,使用功能檢測來利用非同步剪貼簿是個不錯的主意。您可以通過檢查 navigator.clipboard
來檢測對 Async Clipboard API 的支援:
document.addEventListener('paste', async e => {
let text;
if (navigator.clipboard) {
text = await navigator.clipboard.readText()
}
else {
text = e.clipboardData.getData('text/plain');
}
console.log('Got pasted text: ', text);
});
複製程式碼
非同步剪貼簿 API 的下一步是什麼?
正如你可能已經注意到的那樣,這篇文章只涵蓋了 navigator.clipboard
的文字部分。規範中有更多的通用 read()
和 write()
方法,但是這些會帶來額外的實現複雜性和安全性問題(請記住那些影像炸彈?)。目前,Chrome 正在推出更簡單的 API 文字部分。