不要被標題嚇怕了,這篇文章沒有那麼高深。javascript
工做中使用了 clipboard.js 這個庫,實現文本複製的功能。因而對在瀏覽器中實現複製功能的原理產生興趣。html
上網查了一下:vue
docuemnt.execCommand('cpoy')
和 docuemnt.execCommand('cut')
實現剪切和複製功能,它們的做用相似於手動按下快捷鍵 Ctrl + C
和 Ctrl + X
。我有點坐不住了,clipboard.js 到底好在哪裏?懷着深深的疑問,我開始看它的源碼。看過以後,發現無一例外,clipboard.js 也是使用 docuemnt.execCommand
方法實現複製/剪切功能的。java
clipboard.js 核心代碼在 clipboard-action.js 這個文件中,抽離出來後就三行(源碼位於這裏):ios
select(targetElem);
document.execCommand(action) // action 等於 'copy' 或 'cut'
window.getSelection().removeAllRanges()
複製代碼
解釋一下就是:git
第一步中出現的 select()
是引入的一個庫——select.js 中提供的方法。它的做用是選擇元素內容,而且將元素設置爲聚焦狀態(可能的話)。github
下面咱們看看它的實現:web
有趣的是,在查看 clipboard.js 源碼的時候,我發現源碼裏引入了三個依賴:tiny-emitter、select、good-listener。剛開始看的時候,有點抓狂,至於嘛。最後查找了一下這些依賴庫的地址,發現後兩個都是 clipboard.js 做者寫的小庫,前一個是別人寫的。vue-cli
我想做者之因此這樣寫,一方面是爲了得到 IE9+ 以上瀏覽器的支持(IE9 不支持
CustomEvent()
);另外一方面是爲了讓這個庫支持 target 特性——複製指定目標元素裏的內容,最後一點(可能不重要)——自產自銷 😁。瀏覽器
這個庫的代碼量少,五十行都不到。其做用是經過 JavaScript 代碼選擇 HTML 元素的內容。在全局中暴露了一個 select
函數供調用。
使用 demo 以下:
<input type="text" value="my name" onclick="handleSelectInput()">
<script src="select.js></script> <script> function handleSelectInput() { console.log(select(document.querySelector('input'))) } </script> 複製代碼
效果是這樣的:
經過觀察發現,執行 select
函數後,發生了兩件事情:
除了 input
以外,再來看看其餘元素的支持狀況:
下面再來看,功能是如何實現的:
本庫將 HTML 元素分爲三類處理:
<select>
<input>
和 <textarea>
1、<select>
的邏輯
element.focus();
selectedText = element.value;
複製代碼
這裏變量
selectedText
是調用select()
函數後的返回值,下面與此同樣。
<select>
元素當前選中值,能夠從 value
屬性中得到。
2、<input>
和 <textarea
> 的邏輯
var isReadOnly = element.hasAttribute('readonly');
if (!isReadOnly) { // 若是元素不是隻讀的,設置成只讀的
element.setAttribute('readonly', '');
}
element.select();
element.setSelectionRange(0, element.value.length);
selectedText = element.value;
if (!isReadOnly) { // 恢復到以前的狀態
element.removeAttribute('readonly');
}
複製代碼
與 <select>
相似,<input>
和 <textarea>
輸入框中的值,也可經過 value
屬性得到。
其實針對
<input>
和<textarea>
,執行element.select()
就能夠了,就能實現文本框的全選功能了。多是爲了兼容 iOS 系統的緣由,才加了element.setSelectionRange(0, element.value.length)
這麼一句(參見這篇文章)。
3、其餘元素
// 若是元素是 contenteditable 的,先 focus 一下
if (element.hasAttribute('contenteditable')) {
element.focus();
}
var selection = window.getSelection();
var range = document.createRange().selectNodeContents(element) // 生成要選擇的文本範圍(element 裏的內容)
selection.removeAllRanges(); // 先移除當前網頁已有的選擇
selection.addRange(range); // 選擇 element 的內容
selectedText = selection.toString();
複製代碼
select.js 在 clipboard.js 這個庫裏的做用,就是在執行復制/剪切操做以前,選擇好要操做的文本內容。
上面講到的實現過程是基於 target 對象來講的,就是說咱們有目標元素能夠獲取內容。可是若是隻是簡單指定要複製的文本內容,該如何實現的呢?
好比下面這樣:
<button class="btn" data-clipboard-text="Just because you can doesn't mean you should — clipboard.js">
Copy to clipboard
</button>
複製代碼
點擊按鈕的時候,怎樣將 data-clipboard-text
的值複製到剪切板中?
方法就是建立出一個臨時元素(通常使用 <textarea>
),添加到文檔中(不可見),使用它目標元素,在執行完複製操做後,再從文檔中刪除。
相似於下面的代碼(改寫自這裏):
var fakeElem = document.createElement('textarea');
fakeElem.style['left' ] = '-9999px'; // 讓元素在視口以外
fakeElem.setAttribute('readonly', '');
fakeElem.value = text // 這裏的 text 能夠理解爲上面 `data-clipboard-text` 屬性值裏文本內容
document.appendChild(fakeElem) // 將這個臨時元素添加到文檔中
// ... 執行復制操做
複製代碼
僅指定文本進行復制的話,會在文檔中插入一個「不可見」(實際上在視口以外)的臨時元素,做爲目標元素進行操做。
到這裏就把 clipboard.js 核心部分講完了。
其實比較起來,對於我我的而言,我更喜歡 v-clipboard 這個庫中實現的小而美的代碼,而不是 clipboard.js 中的大而全的功能。
可能在心裏裏,小而美的代碼能讓我更快閱讀,知道某個功能的實現細節,不至於出現 bug 時,在解決 bug 的時間上花費太長。固然跟我們具體項目的需求也是有關係的,但知道實現原理更能駕輕就熟一點,不是嘛。
(完)