在Web中, 剪切板的API曾被禁止使用, 如今又開放了相關(guān)接口, 讓我們一起探索吧撼嗓。
在最近的 Web 開發(fā)中, 有遇到使用Clipboard
的場景。即在 B 側(cè) Web 業(yè)務(wù)中, 對于復(fù)雜頁面的配置, 希望提供復(fù)制粘貼
功能镶奉。思考了幾種方案:
-
依賴后臺接口, 新增數(shù)據(jù)
從需求角度來講, 比較簡單的方案就是調(diào)用后臺接口, 生成一條新數(shù)據(jù), 用戶在新增數(shù)據(jù)上進行修改即可阅懦。此方法適用于同一環(huán)境(
product
或devnet
)的復(fù)制粘貼亏推。 -
前端本地存儲, 新增操作時檢測
在用戶觸發(fā)復(fù)制 行為時, 將數(shù)據(jù)存入本地
localStorage
, 當用戶進行新增 操作時, 檢測localStorage
是否有已復(fù)制數(shù)據(jù)。由于是前端保留了復(fù)制 的數(shù)據(jù), 就可以不用考慮后臺的環(huán)境問題, 可以使用測試環(huán)境與現(xiàn)網(wǎng)環(huán)境之間的復(fù)制粘貼。但這里的測試環(huán)境與現(xiàn)網(wǎng)環(huán)境切換依賴了代理配置咏连。對于通過不同域名區(qū)分環(huán)境的應(yīng)用(例如, https://xxx.xx.com or https://test.xxx.xx.com), localStorage 被同源策略限制, 無法跨域讀取數(shù)據(jù)敏弃。 -
使用 Clipboard
在上述前端本地存儲方案的基礎(chǔ)上, 想到了
clipboard
的方案卦羡。類似于淘口令的方案, 將數(shù)據(jù)存入 Clipboard, 然后在新增數(shù)據(jù)時, 檢測 Clipboard 即可。使用 Clipboard 是利用了系統(tǒng)的數(shù)據(jù)存儲, 也解決了不同域名間的跨域問題。
Clipboard 的寫入
document.execCommand
docment.execCommand
是一個可以操作可編輯內(nèi)容區(qū)域
的同步方法绿饵。
// 語法
bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument);
方法第一個參數(shù)aCommandName
傳入命令字, 包括了copy
, cut
, bold
, backColor
等操作欠肾。方法第二個參數(shù)aShowDefaultUI
指是否展示用戶界面, 一般不使用這個參數(shù)。方法第三個參數(shù)aValueArgument
是傳入操作命令時的一些額外參數(shù)拟赊。方法返回了一個 bool 值, 描述操作是否成功刺桃。詳細情況可以參考MDN(https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand)
我們要做的需求是將需要的內(nèi)容寫入 Clipboard, 使用的也就是上述提到的copy
話不多說, 我們通過代碼看下如何使用這個功能
<div>
<input type="text" value="Copy Content"/>
<button onclick="handleClick">copy</button>
</div>
const i = document.querySelector('input');
// 獲得焦點
i.select();
document.execCommand('copy');
點擊按鈕, Copy Content
就會被寫入剪切板, 之后就可以將剪切板內(nèi)容內(nèi)容復(fù)制粘貼到其他地方了。
講到這里, 大家就會好奇, "為什么要用input
組件呢要门?", 當然啦, 其實用textarea
也是可以的虏肾。上述提到了可編輯區(qū)域
, 只有input
, textarea
或具有contenteditable
屬性的元素才可以被execCommand
操作
那如果不想頁面中出現(xiàn)可編輯區(qū)域
, 那可以怎么辦呢?
/**
* @description 復(fù)制內(nèi)容到剪切板
* @param {string} content 文本內(nèi)容
*/
function copy2Clipboard = content => {
const dom = document.createElement('input');
dom.value = content;
document.body.appendChild(dom);
dom.select();
document.execCommand('copy');
document.body.removeChild(dom);
};
可以使用如上取巧方法即可~
navigator.clipboard.writeText
上述document.execCommand
是一個同步操作, navigator.clipboard
是瀏覽器提供的剪切板 API, 可以通過此 API 實現(xiàn)剪切板的操作, 各大瀏覽器廠商最近也都支持了navigator.clipboard
API.
<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 14px;">瀏覽器兼容性</figcaption>
那么如何使用呢? 我們還是從代碼一一道來。
navigator.permissions.query({ name: 'clipboard-write' }).then(function(result) {
// 可能是 'granted', 'denied' or 'prompt':
if (result.state === 'granted') {
// 可以使用權(quán)限
// 進行clipboard的操作
navigator.clipboard.writeText('Clip Content').then(
function() {
/* clipboard successfully set */
// 成功設(shè)置了剪切板
},
function() {
/* clipboard write failed */
// 剪切板內(nèi)容寫入失敗
}
);
} else if (result.state === 'prompt') {
// 彈窗彈框申請使用權(quán)限
} else {
// 如果被拒絕欢搜,請不要做任何操作封豪。
}
});
<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 14px;">剪切板權(quán)限申請?zhí)崾?br> </figcaption>
navigator.permissions
是瀏覽器向用戶請求使用敏感接口的 API, 調(diào)用接口會向用戶彈出 Prompt 框, 詢問用戶是否可以使用相關(guān)權(quán)限。上述代碼先查詢請求使用了clipboard-write
剪切板的使用權(quán)限炒瘟。在權(quán)限通過之后, 調(diào)用了navigator.clipboard.writeText
方法吹埠。
navigator.clipboard
API 被計劃用于取代document.execCommand
接口, 所以也建議使用clipboard
API 去進行復(fù)制操作
。
代碼如下:
async function copy2Clipboard(content) {
const res = await navigator.permissions.query({ name: 'clipboard-write' });
if (res.state === 'granted') {
return navigator.clipboard.writeText(content);
}
return Promise.reject(res);
}
Clipboard 的讀取
document.execCommand
考慮到安全原因, document.execCommand('paste')
操作已經(jīng)被禁止了疮装。
那有什么辦法可以讀取剪切板的內(nèi)容呢?
監(jiān)聽 paste 事件
document.addEventListener('paste', event => {
// 從event中讀取clipboardData
const pasteContent = (event.clipboardData || window.clipboardData).getData('text');
// do whatever
});
在本需求場景中, 希望可以由前端讀取的剪切板內(nèi)容, 而不是用戶主動觸發(fā), 所以這里就不再詳述了缘琅。
那還有什么方案呢?
navigator.clipboard.readText
navigator.clipboard.readText
也是瀏覽器提供的剪切板操作 API, 與writeText
類似, 也需要請求剪切板權(quán)限。
// 申請使用剪切板讀取權(quán)限
navigator.permissions.query({ name: 'clipboard-read' }).then(function(result) {
// 可能是 'granted', 'denied' or 'prompt':
if (result.state === 'granted') {
// 可以使用權(quán)限
// 進行clipboard的操作
navigator.clipboard
.readText()
.then(text => {
console.log('復(fù)制粘貼文本: ', text);
})
.catch(err => {
// 讀取剪切板內(nèi)容失敗
console.error('Failed to read clipboard contents: ', err);
});
} else if (result.state === 'prompt') {
// 彈窗彈框申請使用權(quán)限
} else {
// 如果被拒絕廓推,請不要做任何操作刷袍。
}
});
首先我們要申請使用剪切板的clipboard-read
權(quán)限, 在獲得用戶權(quán)限后, 即可通過navigator.clipboard.readText
獲取權(quán)限了。
當然監(jiān)聽paste
事件也是可以的
document.addEventListener('paste', event => {
event.preventDefault();
navigator.clipboard.readText().then(text => {
console.log('Pasted text: ', text);
});
});
因此, 我們就可以將讀取剪切板內(nèi)容的功能抽象出來:
/**
* @description 讀取剪切板內(nèi)容
* @return {string}
*/
async function readClipboard() {
const result = await navigator.permissions.query({ name: 'clipboard-read' });
if (result.state === 'granted' || result.state === 'prompt') {
return navigator.clipboard
.readText()
.then(text => text)
.catch(err => Promise.reject(err));
}
return Promise.reject(result);
}
總結(jié)與注意
清空剪切板內(nèi)容
瀏覽器并沒有提供可以清理剪切板的接口樊展。如果網(wǎng)站在使用完剪切板內(nèi)容后, 需要進行清理內(nèi)容的話, 可以重新寫入數(shù)據(jù)
// ...
input.value = ' '; // input的值必須有值, 不能是空字符串
input.select();
document.execCommand('copy')
// 或者使用clipboard
navigator.clipboard.writeText('');
安全問題
Web操作剪切板內(nèi)容具有一定的安全風險呻纹。瀏覽器為了保證用戶隱私, 要求使用navigator.clipboard
API必須要接入HTTPS
。
HTTP網(wǎng)站是不支持此接口的, 僅支持document.execCommand('copy')
和監(jiān)聽paste
事件
從用戶角度考慮, 也建議大家的網(wǎng)站都接入HTTPS