當(dāng)我們使用 GitHub 時(shí),會(huì)發(fā)現(xiàn) Ctrl+V 就能直接讀取用戶剪貼板圖片進(jìn)行粘貼,那么它是如何工作的?安全性如何?
新一代神器:navigator.clipboard
navigator.clipboard
API 是一個(gè)異步的、基于 Promise 的現(xiàn)代接口,其具有三大核心優(yōu)勢(shì):
- 所有讀寫(xiě)操作都返回 Promise,不會(huì)阻塞頁(yè)面
- 操作剪貼板需要獲得用戶授權(quán),既安全又透明
- 除了文本,它能輕松讀寫(xiě)圖片等二進(jìn)制數(shù)據(jù)
寫(xiě)入剪貼板
在深入讀取操作前,我們先看看如何向剪貼板寫(xiě)入內(nèi)容。
寫(xiě)入文本
這是最常見(jiàn)的操作,比如實(shí)現(xiàn)一個(gè)“點(diǎn)擊復(fù)制”按鈕。
const copyBtn = document.getElementById('copy-btn');
copyBtn.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText('你好,世界!');
console.log('文本已成功復(fù)制到剪貼板');
} catch (err) {
console.error('復(fù)制失敗: ', err);
}
});
寫(xiě)入圖片
寫(xiě)入圖片稍微復(fù)雜一點(diǎn),需要使用 clipboard.write()
方法,并傳入一個(gè) ClipboardItem
對(duì)象。這個(gè)對(duì)象可以包含不同 MIME 類(lèi)型的數(shù)據(jù)。
讀取剪貼板
當(dāng)用戶在我們的網(wǎng)頁(yè)上執(zhí)行粘貼操作時(shí),我們?nèi)绾巫x取剪貼板里的內(nèi)容,特別是圖片?
關(guān)鍵:獲取用戶授權(quán)
這是最重要的一步。當(dāng)我們嘗試讀取剪貼板時(shí),瀏覽器會(huì)主動(dòng)彈出提示框,請(qǐng)求用戶授權(quán)。
只有用戶點(diǎn)擊“允許”,我們的代碼才能繼續(xù)執(zhí)行。這徹底杜絕了惡意網(wǎng)站在后臺(tái)偷偷讀取用戶剪貼板內(nèi)容的可能。
讀取步驟詳解
讓我們來(lái)實(shí)現(xiàn)一個(gè)監(jiān)聽(tīng)粘貼事件并處理圖片的功能。
- 監(jiān)聽(tīng)
paste
事件 - 調(diào)用
navigator.clipboard.read()
:該方法會(huì)返回 Promise,解析為一個(gè) ClipboardItem
對(duì)象的數(shù)組 - 檢查每個(gè)
ClipboardItem
MIME 類(lèi)型 - 獲取數(shù)據(jù)
Blob
:如果 MIME 中包含我們想要的圖片類(lèi)型(如 "image/png"
),我們就可以調(diào)用 item.getType()
方法解析為該數(shù)據(jù)的 Blob
對(duì)象 - 拿到
Blob
對(duì)象后,最常見(jiàn)的做法是使用 URL.createObjectURL()
生成一個(gè)臨時(shí) URL,并將其顯示在 <img>
標(biāo)簽中,或直接上傳到服務(wù)器
完整代碼示例
假設(shè)我們有這樣一個(gè) HTML 結(jié)構(gòu):
<div id="paste-area" contenteditable="true">
<p>請(qǐng)?jiān)谶@里粘貼你的截圖...</p>
</div>
<img id="preview-image" src="" alt="圖片預(yù)覽" style="max-width: 100%; margin-top: 20px;">
對(duì)應(yīng)的 JavaScript 代碼如下:
const pasteArea = document.getElementById('paste-area');
const previewImage = document.getElementById('preview-image');
pasteArea.addEventListener('paste', async (e) => {
// 阻止默認(rèn)的粘貼行為
e.preventDefault();
try {
// 請(qǐng)求讀取剪貼板的權(quán)限
const permission = await navigator.permissions.query({ name: 'clipboard-read' });
if (permission.state === 'denied') {
throw new Error('剪貼板讀取權(quán)限被拒絕');
}
// 讀取剪貼板內(nèi)容
const clipboardItems = await navigator.clipboard.read();
for (const item of clipboardItems) {
// 檢查是否有圖片類(lèi)型
const imageType = item.types.find(type => type.startsWith('image/'));
if (imageType) {
// 獲取圖片 blob 數(shù)據(jù)
const blob = await item.getType(imageType);
// 創(chuàng)建一個(gè)臨時(shí)的 URL 來(lái)預(yù)覽圖片
const imageUrl = URL.createObjectURL(blob);
previewImage.src = imageUrl;
// 在這里,你可以將 blob 上傳到服務(wù)器
// uploadToServer(blob);
console.log('圖片粘貼成功!');
return; // 處理完圖片后即可退出
}
}
// 如果沒(méi)有圖片,可以嘗試讀取文本
const text = await navigator.clipboard.readText();
console.log('粘貼的文本內(nèi)容:', text);
pasteArea.innerText = text;
} catch (err) {
console.error('粘貼失敗: ', err);
// 如果沒(méi)有圖片,嘗試用傳統(tǒng)方式讀取文本
const text = e.clipboardData.getData('text/plain');
if (text) {
pasteArea.innerText = text;
}
}
});
function uploadToServer(blob) {
const formData = new FormData();
formData.append('image', blob, 'screenshot.png');
// fetch('/api/upload', {
// method: 'POST',
// body: formData
// }).then(...);
console.log('正在模擬上傳...', formData.get('image'));
}
現(xiàn)在,截取一張圖到剪貼板,然后在這個(gè)區(qū)域按下 Ctrl+V
,你會(huì)看到瀏覽器彈出權(quán)限請(qǐng)求。授權(quán)后,圖片就會(huì)立刻顯示在下方!
該文章在 2025/7/30 9:48:46 編輯過(guò)