更安全地存取剪貼簿的文字和圖片
傳統的存取系統剪貼簿的方法是透過
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);
}
}
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);
}
複製事件
在使用者啟動剪貼簿副本時
且「不會」呼叫 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
事件:
針對 ClipboardItem
:
貼上:從剪貼簿讀取資料
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);
}
}
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);
}
}
使用貼上的檔案
對於使用者來說,使用者可以使用剪貼簿鍵盤快速鍵,例如 ctrl+c 和 ctrl+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());
});
貼上事件
如前所述,我們計劃推出適用於 Clipboard API 的事件,
但現在您可以使用現有的 paste
事件。與新的應用程式
讀取剪貼簿文字的非同步方法與 copy
事件一樣,請避免
忘記呼叫 preventDefault()
。
document.addEventListener('paste', async (e) => {
e.preventDefault();
const text = await navigator.clipboard.readText();
console.log('Pasted text: ', text);
});
處理多種 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 /
或
解壓縮炸彈圖片
複製到剪貼簿。
讓網頁內容不受限的讀取權限也更加容易 麻煩。使用者會定期複製密碼等機密資訊 傳送至剪貼簿,方便任何沒有 對使用者的告知
如同許多新的 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-read
或 clipboard-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 實作 Huang和Gary Kačmarčík。Darwin 也提供示範 非常感謝Kyarik。再次感謝 Gary Kačmarčík 致敬 深入探討本文的某些部分
主頁橫幅由 Markus Winkler 提供 Unsplash。