在最近的 Web 開發中, 有遇到使用Clipboard的場景。即在 B 側 Web 業務中, 對於復雜頁面的配置, 希望提供復制粘貼功能。 思考了幾種方案:
-
依賴后台接口, 新增數據 從需求角度來講, 比較簡單的方案就是調用后台接口, 生成一條新數據, 用戶在新增數據上進行修改即可。此方法適用於同一環境(
product或devnet)的復制粘貼。 -
前端本地存儲, 新增操作時檢測 在用戶觸發復制行為時, 將數據存入本地
localStorage, 當用戶進行新增操作時, 檢測localStorage是否有已復制數據。由於是前端保留了復制的數據, 就可以不用考慮后台的環境問題, 可以使用測試環境與現網環境之間的復制粘貼。 但這里的測試環境與現網環境切換依賴了代理配置。對於通過不同域名區分環境的應用(例如, xxx.xx.com or test.xxx.xx.com), localStorage 被同源策略限制, 無法跨域讀取數據。 -
使用 Clipboard 在上述前端本地存儲方案的基礎上, 想到了
clipboard的方案。類似於淘口令的方案, 將數據存入 Clipboard, 然后在新增數據時, 檢測 Clipboard 即可。使用 Clipboard 是利用了系統的數據存儲, 也解決了不同域名間的跨域問題。
Clipboard 的寫入
document.execCommand
docment.execCommand是一個可以操作可編輯內容區域的同步方法。 // 語法 bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument);
方法第一個參數aCommandName傳入命令字, 包括了copy, cut, bold, backColor等操作。方法第二個參數aShowDefaultUI指是否展示用戶界面, 一般不使用這個參數。方法第三個參數aValueArgument是傳入操作命令時的一些額外參數。方法返回了一個 bool 值, 描述操作是否成功。詳細情況可以參考MDN
我們要做的需求是將需要的內容寫入 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就會被寫入剪切板, 之后就可以將剪切板內容內容復制粘貼到其他地方了。
講到這里, 大家就會好奇, "為什么要用input組件呢?", 當然啦, 其實用textarea也是可以的。上述提到了可編輯區域, 只有input, textarea或具有contenteditable屬性的元素才可以被execCommand操作
那如果不想頁面中出現可編輯區域, 那可以怎么辦呢?
/** * @description 復制內容到剪切板 * @param {string} content 文本內容 */ 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 實現剪切板的操作, 各大瀏覽器廠商最近也都支持了navigator.clipboardAPI.
那么如何使用呢? 我們還是從代碼一一道來。
navigator.permissions.query({ name: 'clipboard-write' }).then(function(result) {
// 可能是 'granted', 'denied' or 'prompt':
if (result.state === 'granted') {
// 可以使用權限
// 進行clipboard的操作
navigator.clipboard.writeText('Clip Content').then(
function() {
/* clipboard successfully set */
// 成功設置了剪切板
},
function() {
/* clipboard write failed */
// 剪切板內容寫入失敗
}
);
} else if (result.state === 'prompt') {
// 彈窗彈框申請使用權限
} else {
// 如果被拒絕,請不要做任何操作。
}
});
navigator.permissions是瀏覽器向用戶請求使用敏感接口的 API, 調用接口會向用戶彈出 Prompt 框, 詢問用戶是否可以使用相關權限。 上述代碼先查詢請求使用了clipboard-write剪切板的使用權限。 在權限通過之后, 調用了navigator.clipboard.writeText方法。
navigator.clipboardAPI 被計划用於取代document.execCommand接口, 所以也建議使用clipboardAPI 去進行復制操作。
代碼如下:
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); }
關注【IVWEB社區】公眾號查看最新技術周刊,做更優秀的自己!
Clipboard 的讀取
document.execCommand
考慮到安全原因, document.execCommand('paste')操作已經被禁止了。
那有什么辦法可以讀取剪切板的內容呢?
監聽 paste 事件
document.addEventListener('paste', event => {
// 從event中讀取clipboardData
const pasteContent = (event.clipboardData || window.clipboardData).getData('text');
// do whatever
});
在本需求場景中, 希望可以由前端讀取的剪切板內容, 而不是用戶主動觸發, 所以這里就不再詳述了。
那還有什么方案呢?
navigator.clipboard.readText
navigator.clipboard.readText也是瀏覽器提供的剪切板操作 API, 與writeText類似, 也需要請求剪切板權限。
// 申請使用剪切板讀取權限 navigator.permissions.query({ name: 'clipboard-read' }).then(function(result) { // 可能是 'granted', 'denied' or 'prompt': if (result.state === 'granted') { // 可以使用權限 // 進行clipboard的操作 navigator.clipboard .readText() .then(text => { console.log('復制粘貼文本: ', text); }) .catch(err => { // 讀取剪切板內容失敗 console.error('Failed to read clipboard contents: ', err); }); } else if (result.state === 'prompt') { // 彈窗彈框申請使用權限 } else { // 如果被拒絕,請不要做任何操作。 } });
首先我們要申請使用剪切板的clipboard-read權限, 在獲得用戶權限后, 即可通過navigator.clipboard.readText獲取權限了。
當然監聽paste事件也是可以的
document.addEventListener('paste', event => {
event.preventDefault();
navigator.clipboard.readText().then(text => {
console.log('Pasted text: ', text);
});
});
因此, 我們就可以將讀取剪切板內容的功能抽象出來:
/** * @description 讀取剪切板內容 * @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); }
總結與注意
清空剪切板內容
瀏覽器並沒有提供可以清理剪切板的接口。如果網站在使用完剪切板內容后, 需要進行清理內容的話, 可以重新寫入數據
// ... input.value = ' '; // input的值必須有值, 不能是空字符串 input.select(); document.execCommand('copy') // 或者使用clipboard navigator.clipboard.writeText('');
安全問題
Web操作剪切板內容具有一定的安全風險。瀏覽器為了保證用戶隱私, 要求使用navigator.clipboardAPI必須要接入HTTPS。
HTTP網站是不支持此接口的, 僅支持document.execCommand('copy')和監聽paste事件
從用戶角度考慮, 也建議大家的網站都接入HTTPS
clipboard的未來
可能會支持更通用的write與read方法, 提供二進制數據的寫入等等(例如圖片等)。
參考
作者:騰訊IVWEB團隊,鏈接:https://juejin.im/post/5dea4292518825122c4c9b8e
