clipboard.js 核心代碼解析

不要被標題嚇怕了,這篇文章沒有那麼高深。javascript

工做中使用了 clipboard.js 這個庫,實現文本複製的功能。因而對在瀏覽器中實現複製功能的原理產生興趣。html

上網查了一下:vue

  1. 讀了這篇文章,知道能夠經過 docuemnt.execCommand('cpoy')docuemnt.execCommand('cut') 實現剪切和複製功能,它們的做用相似於手動按下快捷鍵 Ctrl + CCtrl + X
  2. 還看了一個 Vue 指令庫 v-clipboard 的源碼(源碼量不多,功能也很簡單),知道是如何實現的了。
  3. 又看了另外一個 Vue 指令庫 vue-clipboard2,star 數量快一千了,這個庫是對 clipboard.js 的一層封裝。

我有點坐不住了,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

  1. 選擇元素
  2. 執行復制/剪切(相似於按下 Ctrl + C/Ctrl + X)
  3. 取消元素選擇

第一步中出現的 select() 是引入的一個庫——select.js 中提供的方法。它的做用是選擇元素內容,而且將元素設置爲聚焦狀態(可能的話)。github

下面咱們看看它的實現:web

有趣的是,在查看 clipboard.js 源碼的時候,我發現源碼裏引入了三個依賴:tiny-emitterselectgood-listener。剛開始看的時候,有點抓狂,至於嘛。最後查找了一下這些依賴庫的地址,發現後兩個都是 clipboard.js 做者寫的小庫,前一個是別人寫的。vue-cli

我想做者之因此這樣寫,一方面是爲了得到 IE9+ 以上瀏覽器的支持(IE9 不支持 CustomEvent());另外一方面是爲了讓這個庫支持 target 特性——複製指定目標元素裏的內容,最後一點(可能不重要)——自產自銷 😁。瀏覽器

select.js

這個庫的代碼量少,五十行都不到。其做用是經過 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 函數後,發生了兩件事情:

  1. 元素被聚焦(能夠的話)
  2. 選擇元素內容(而且內容以函數返回值的形式返回)

除了 input 以外,再來看看其餘元素的支持狀況:

下面再來看,功能是如何實現的:

代碼實現

本庫將 HTML 元素分爲三類處理:

  1. <select>
  2. <input><textarea>
  3. 其餘元素

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 的時間上花費太長。固然跟我們具體項目的需求也是有關係的,但知道實現原理更能駕輕就熟一點,不是嘛。

(完)

相關文章
相關標籤/搜索