在寫了 這個 29.7 K 的剪貼板 JS 庫有點東西! 這篇文章以後,收到了小夥伴提的兩個問題:javascript
1.clipboard.js 這個庫除了複製文字以外,能複製圖像麼?html
2.clipboard.js 這個庫依賴的 document.execCommand API 已被廢棄了,之後應該怎麼辦?java
(圖片來源:https://developer.mozilla.org...)git
接下來,本文將圍繞上述兩個問題展開,不過在看第一個問題以前,咱們先來簡單介紹一下 剪貼板 📋。github
剪貼板(英語:clipboard),有時也稱剪切板、剪貼簿、剪貼本。它是一種軟件功能,一般由操做系統提供, 做用是使用複製和粘貼操做短時間存儲數據和在文檔或應用程序間轉移數據。它是圖形用戶界面(GUI)環境中最經常使用的功能之一,一般實現爲匿名、臨時的數據緩衝區,能夠被環境內的大部分或全部程序使用編程接口訪問。 —— 維基百科
經過以上的描述咱們能夠知道,剪貼板架起了一座橋樑,使得在各類應用程序之間,傳遞和共享信息成爲可能。然而美中不足的是,剪貼板只能保留一份數據,每當新的數據傳入,舊的便會被覆蓋。web
瞭解完 剪貼板 📋 的概念和做用以後,咱們立刻來看一下第一個問題:clipboard.js 這個庫除了複製文字以外,能複製圖像麼?編程
關注「全棧修仙之路」閱讀阿寶哥原創的 4 本免費電子書(累計下載近2.1萬)及 50 幾篇 「重學TS」 教程。
clipboard.js 是一個用於將 文本 複製到剪貼板的 JS 庫。沒有使用 Flash,沒有使用任何框架,開啓 gzipped 壓縮後僅僅只有 3kb。api
(圖片來源:https://clipboardjs.com/#exam...)瀏覽器
當你看到 「A modern approach to copy text to clipboard」 這個描述,你是否是已經知道答案了。那麼實際的狀況是怎樣呢?下面咱們來動手驗證一下。在 這個 29.7 K 的剪貼板 JS 庫有點東西! 這篇文章中,阿寶哥介紹了在實例化 ClipboardJS
對象時,能夠經過 options
對象的 target
屬性來設置複製的目標:安全
// https://github.com/zenorocha/clipboard.js/blob/master/demo/function-target.html let clipboard = new ClipboardJS('.btn', { target: function() { return document.querySelector('div'); } });
利用 clipboard.js 的這個特性,咱們能夠定義如下 HTML 結構:
<div id="container"> <img src="http://cdn.semlinker.com/abao.png" width="80" height="80"/> <p>你們好,我是阿寶哥</p> </div> <button class="btn">複製</button>
而後在實例化 ClipboardJS
對象時設置複製的目標是 #container
元素:
const clipboard = new ClipboardJS(".btn", { target: function () { return document.querySelector("#container"); } });
以後,咱們點擊頁面中的 複製 按鈕,對應的效果以下圖所示:
觀察上圖可知,頁面中的圖像和文本都已經被複制了。對於文原本說,你們應該都很清楚。而對於圖像來講,到底複製了什麼?咱們又該如何獲取已複製的內容呢?針對這個問題,咱們能夠利用 HTMLElement
對象上的 onpaste 屬性或者監聽元素上的 paste 事件。
這裏咱們經過設置 document
對象的 onpaste 屬性,來打印一下粘貼事件對應的事件對象:
document.onpaste = function (e) { console.dir(e); }
當咱們點擊 複製 按鈕,而後在頁面執行 粘貼 操做後,控制檯會打印出如下內容:
經過上圖可知,在 ClipboardEvent
對象中含有一個 clipboardData
屬性,該屬性包含了與剪貼板相關聯的數據。詳細分析了 clipboardData
屬性以後,咱們發現已複製的圖像和普通文本被封裝爲 DataTransferItem 對象。
爲了更方便地分析 DataTransferItem 對象,阿寶哥從新更新了 document
對象的 onpaste 屬性:
在上圖中,咱們能夠清楚的看到 DataTransferItem 對象上含有 kind
和 type
屬性分別用於表示數據項的類型(string 或 file)及數據對應的 MIME 類型。利用 DataTransferItem 對象提供的 getAsString
方法,咱們能夠獲取該對象中保存的數據:
相信看完以上的輸出結果,小夥伴們就很清楚第一個問題的答案了。那麼若是想要複製圖像的話,應該如何實現呢?其實這個問題的答案與小夥伴提的第二個問題的答案是同樣的,咱們能夠利用 Clipboard API 來實現複製圖像的問題及解決 document.execCommand API 已被廢棄的問題。
接下來,咱們的目標就是實現複製圖像的功能了,由於要利用到 Clipboard API,因此阿寶哥先來介紹一下該 API。
Clipboard
接口實現了 Clipboard API,若是用戶授予了相應的權限,就能提供系統剪貼板的讀寫訪問。在 Web 應用程序中,Clipboard API 可用於實現剪切、複製和粘貼功能。該 API 用於取代經過 document.execCommand API 來實現剪貼板的操做。
在實際項目中,咱們不須要手動建立 Clipboard
對象,而是經過 navigator.clipboard
來獲取 Clipboard
對象:
在獲取 Clipboard
對象以後,咱們就能夠利用該對象提供的 API 來訪問剪貼板,好比:
navigator.clipboard.readText().then( clipText => document.querySelector(".editor").innerText = clipText);
以上代碼將 HTML 中含有 .editor
類的第一個元素的內容替換爲剪貼板的內容。若是剪貼板爲空,或者不包含任何文本,則元素的內容將被清空。這是由於在剪貼板爲空或者不包含文本時,readText
方法會返回一個空字符串。
在繼續介紹 Clipboard API 以前,咱們先來看一下 Navigator API: clipboard 的兼容性:
(圖片來源:https://caniuse.com/mdn-api_n...)
異步剪貼板 API 是一個相對較新的 API,瀏覽器仍在逐漸實現它。因爲潛在的安全問題和技術複雜性,大多數瀏覽器正在逐步集成這個 API。對於瀏覽器擴展來講,你能夠請求 clipboardRead 和 clipboardWrite 權限以使用 clipboard.readText() 和 clipboard.writeText()。
好的,接下來阿寶哥來演示一下如何使用 clipboard
對象提供的 API 來操做剪貼板,如下示例的運行環境是 Chrome 87.0.4280.88。
writeText 方法能夠把指定的字符串寫入到系統的剪貼板中,調用該方法後會返回一個 Promise 對象:
<button onclick="copyPageUrl()">拷貝當前頁面地址</button> <script> async function copyPageUrl() { try { await navigator.clipboard.writeText(location.href); console.log("頁面地址已經被拷貝到剪貼板中"); } catch (err) { console.error("頁面地址拷貝失敗: ", err); } } </script>
對於上述代碼,當用戶點擊 拷貝當前頁面地址 按鈕時,將會把當前的頁面地址拷貝到剪貼板中。
write 方法除了支持文本數據以外,還支持將圖像數據寫入到剪貼板,調用該方法後會返回一個 Promise 對象。
<button onclick="copyPageUrl()">拷貝當前頁面地址</button> <script> async function copyPageUrl() { const text = new Blob([location.href], {type: 'text/plain'}); try { await navigator.clipboard.write( new ClipboardItem({ "text/plain": text, }), ); console.log("頁面地址已經被拷貝到剪貼板中"); } catch (err) { console.error("頁面地址拷貝失敗: ", err); } } </script>
在以上代碼中,咱們先經過 Blob API 建立 Blob 對象,而後使用該 Blob 對象來構造 ClipboardItem
對象,最後再經過 write
方法把數據寫入到剪貼板。介紹完如何將數據寫入到剪貼板,下面咱們來介紹如何從剪貼板中讀取數據。
對 Blob API 感興趣的小夥伴,能夠閱讀 你不知道的 Blob 這篇文章。
readText 方法用於讀取剪貼板中的文本內容,調用該方法後會返回一個 Promise 對象:
<button onclick="getClipboardContents()">讀取剪貼板中的文本</button> <script> async function getClipboardContents() { try { const text = await navigator.clipboard.readText(); console.log("已讀取剪貼板中的內容:", text); } catch (err) { console.error("讀取剪貼板內容失敗: ", err); } } </script>
對於上述代碼,當用戶點擊 讀取剪貼板中的文本 按鈕時,若是當前剪貼板含有文本內容,則會讀取剪貼板中的文本內容。
read 方法除了支持讀取文本數據以外,還支持讀取剪貼板中的圖像數據,調用該方法後會返回一個 Promise 對象:
<button onclick="getClipboardContents()">讀取剪貼板中的內容</button> <script> 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("已讀取剪貼板中的內容:", await blob.text()); } } } catch (err) { console.error("讀取剪貼板內容失敗: ", err); } } </script>
對於上述代碼,當用戶點擊 讀取剪貼板中的內容 按鈕時,則會開始讀取剪貼板中的內容。到這裏 clipboard
對象中涉及的 4 個 API,阿寶哥都已經介紹完了,最後咱們來看一下如何實現複製圖像的功能。
在最後的這個示例中,阿寶哥將跟你們一步步實現複製圖像的核心功能,除了複製圖像以外,還會同時支持複製文本。在看具體代碼前,咱們先來看一下實際的效果:
在上圖對應的網頁中,咱們先點擊 複製 按鈕,則圖像和文本都會被選中。以後,咱們在點擊 粘貼 按鈕,則控制檯會輸出從剪貼板中讀取的實際內容。在分析具體的實現方式前,咱們先來看一下對應的頁面結構:
<div id="container"> <img src="http://cdn.semlinker.com/abao.png" width="80" height="80" /> <p>你們好,我是阿寶哥</p> </div> <button onclick="writeDataToClipboard()">複製</button> <button onclick="readDataFromClipboard()">粘貼</button>
上面的頁面結構很簡單,下一步咱們來逐步分析一下以上功能的實現過程。
默認狀況下,會爲當前的激活的頁面自動授予剪貼板的寫入權限。出於安全方面考慮,這裏咱們仍是主動向用戶請求剪貼板的寫入權限:
async function askWritePermission() { try { const { state } = await navigator.permissions.query({ name: "clipboard-write", }); return state === "granted"; } catch (error) { return false; } }
要往剪貼板寫入圖像數據,咱們就須要使用 navigator.clipboard
對象提供的 write 方法。若是要寫入圖像數據,咱們就須要獲取該圖像對應的 Blob 對象,這裏咱們能夠經過 fetch
API 從網絡上獲取圖像對應的響應對象並把它轉化成 Blob 對象,具體實現方式以下:
async function createImageBlob(url) { const response = await fetch(url); return await response.blob(); }
而對於普通文原本說,只須要使用前面介紹的 Blob API 就能夠把普通文本轉換爲 Blob 對象:
function createTextBlob(text) { return new Blob([text], { type: "text/plain" }); }
在建立完圖像和普通文本對應的 Blob 對象以後,咱們就能夠利用它們來建立 ClipboardItem
對象,而後再調用 write 方法把這些數據寫入到剪貼板中,對應的代碼以下所示:
async function writeDataToClipboard() { if (askWritePermission()) { if (navigator.clipboard && navigator.clipboard.write) { const textBlob = createTextBlob("你們好,我是阿寶哥"); const imageBlob = await createImageBlob( "http://cdn.semlinker.com/abao.png" ); try { const item = new ClipboardItem({ [textBlob.type]: textBlob, [imageBlob.type]: imageBlob, }); select(document.querySelector("#container")); await navigator.clipboard.write([item]); console.log("文本和圖像複製成功"); } catch (error) { console.error("文本和圖像複製失敗", error); } } } }
在以上代碼中,使用了一個 select
方法,該方法用於實現選擇的效果,對應的代碼以下所示:
function select(element) { const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(element); selection.removeAllRanges(); selection.addRange(range); }
經過 writeDataToClipboard
方法,咱們已經把圖像和普通文本數據寫入剪貼板了。下面咱們來使用 navigator.clipboard
對象提供的 read
方法,來讀取已寫入的數據。若是你須要讀取剪貼板的數據,則須要向用戶請求 clipboard-read
權限。
這裏咱們定義了一個 askReadPermission
函數來向用戶請求剪貼板讀取權限:
async function askReadPermission() { try { const { state } = await navigator.permissions.query({ name: "clipboard-read", }); return state === "granted"; } catch (error) { return false; } }
當調用 askReadPermission
方法後,將會向當前用戶請求剪貼板讀取權限,對應的效果以下圖所示:
建立好 askReadPermission
函數,咱們就能夠利用以前介紹的 navigator.clipboard.read
方法來讀取剪貼板的數據了:
async function readDataFromClipboard() { if (askReadPermission()) { if (navigator.clipboard && navigator.clipboard.read) { try { const clipboardItems = await navigator.clipboard.read(); for (const clipboardItem of clipboardItems) { console.dir(clipboardItem); for (const type of clipboardItem.types) { const blob = await clipboardItem.getType(type); console.log("已讀取剪貼板中的內容:", await blob.text()); } } } catch (err) { console.error("讀取剪貼板內容失敗: ", err); } } } }
其實,除了點擊 粘貼 按鈕以外,咱們還能夠經過監聽 paste
事件來讀取剪貼板中的數據。須要注意的是,若是當前的瀏覽器不支持異步 Clipboard API,咱們能夠經過 clipboardData.getData
方法來讀取剪貼板中的文本數據:
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('已獲取的文本數據: ', text); });
而對於圖像數據,則能夠經過如下方式進行讀取:
const IMAGE_MIME_REGEX = /^image\/(p?jpeg|gif|png)$/i; document.addEventListener("paste", async (e) => { e.preventDefault(); if (navigator.clipboard) { let clipboardItems = await navigator.clipboard.read(); for (const clipboardItem of clipboardItems) { for (const type of clipboardItem.types) { if (IMAGE_MIME_REGEX.test(type)) { const blob = await clipboardItem.getType(type); loadImage(blob); return; } } } } else { const items = e.clipboardData.items; for (let i = 0; i < items.length; i++) { if (IMAGE_MIME_REGEX.test(items[i].type)) { loadImage(items[i].getAsFile()); return; } } } });
以上代碼中的 loadImage
方法用於實現把複製的圖片插入到當前選區已選擇的區域中,對應的代碼以下:
function loadImage(file) { const reader = new FileReader(); reader.onload = function (e) { let img = document.createElement("img"); img.src = e.target.result; let range = window.getSelection().getRangeAt(0); range.deleteContents(); range.insertNode(img); }; reader.readAsDataURL(file); }
在前面代碼中,咱們監聽了 document
對象的 paste
事件。除了該事件以外,與剪貼板相關的常見事件還有 copy
和 cut
事件。篇幅有限,阿寶哥就不繼續展開介紹了,感興趣的小夥伴能夠自行閱讀相關資料。好的,至此本文就已經結束了,但願閱讀完本文以後,你們對異步的 Clipboard API 會有些瞭解,有寫得不清楚的地方,歡迎你隨時跟阿寶哥交流喲。
關注「全棧修仙之路」閱讀阿寶哥原創的 4 本免費電子書(累計下載近 2萬)及 9 篇源碼分析系列教程。