# el-autocomplete核心參數 可以實現非同步的數據拉取,從非同步返回的數據中,選擇需要的結果,並回顯到文本框中。 ## fetch-suggestions 回調列表,非同步的方式獲取數據列表,顯示在列表框中 ## @select 當選中某一項時,會觸發這個方法,將數據獲取到,這時,我們可以將 ...
1.功能需求
實習工作中,遇到一個需求,需要完成點擊複製的功能,當文字過長的時候,讓用戶手拖再ctrl+c這種方式體驗就不是很好了,如果可以點擊一下直接複製就是一種不錯的優化用戶體驗的方式。
經過查閱文檔,網路上完成這個功能大多使用兩大類方法
第一種是以document.execCommand() 方法為主,無論是手寫還是使用clipboard.js插件都是依賴的這個方法,但是在MDN 文檔中已經顯示過時了。
第二種是用了navigator.clipboard的方法,避免了過時問題,但是在複製圖片的時候會有一定的瀏覽器相容性問題
2.document.execCommand('copy')
這個方法其實就是在模擬用戶選擇元素然後右鍵複製的動作。儘管MDN已經顯示這個方法過時了,但是僅針對copy這個指令,大部分主流瀏覽器都可以支持,所以這個方法仍然可以作為一種實現問題的方案。
2.1 基本用法
根據MDN文檔學習本方法的傳參和返回值
語法
bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)
這個方法可以傳3個參數,並且會返回一個布爾值
返回值
先從返回值開始,返回值相對比較簡單,如果返回的值是false就表示瀏覽器不支持使用這個操作,反之瀏覽器支持該操作就返回true。
雖然這個返回值看似可以用來提前判斷瀏覽器相容性,但是文檔中不推薦在調用一個命令前,嘗試使用返回值去校驗瀏覽器的相容性
參數值
參數一共可以傳3個,但是使用複製命令的時候只需要傳第一個參數就可以。這裡簡單介紹一下3個參數
- aCommandName:一個字元串類型的參數,是命令的名稱,比如複製用到的copy,剪切用到的cut
- aShowDefaultUI:一個布爾類型的參數,表示是否展示用戶界面,一般為false,Mozilla 沒有實現
- aValueArgument:一些命令(例如 insertImage)需要額外的參數(insertImage 需要提供插入 image 的 url),預設為 null。
簡單舉例
以本文主要講的複製命令為例子:document.execCommand('copy')
指令相容性問題
前文講到,MDN不推薦在調用一個命令前,嘗試使用返回值去校驗瀏覽器的相容性,那麼就需要用另外的方法去檢測瀏覽器是否支持某個指令,瀏覽器為我們提供了一個方法叫document.queryCommandSupported(),使用這個方法可以檢測瀏覽器是否支持某個指令,這個方法比較簡單,只有1個參數,參數就傳指令字元串,方法的返回值是一個布爾值表示當前瀏覽器是否支持這個指令。
舉例如下:
if(document.queryCommandSupported && document.queryCommandSupported('copy')){
//先檢測是否支持document.queryCommandSupported和copy指令
//如果都支持直接執行指令
document.execCommand('copy')
}
MDN文檔中提到,document.queryCommandSupported也被棄用了,但是為了相容性依然保留可用,當我們使用document.execCommand的時候仍然可以用document.queryCommandSupported來檢測是否支持。同時,它的瀏覽器相容性也是比較好的,大部分主流瀏覽器都支持。
2.2 Selection Api
複製文本這個操作對比複製圖片是相對比較簡單的,一共包含2大步
一是選中要複製的元素
二是執行複製指令。
執行複製指令在前面的基本語法里已經講到了,直接調用document.execCommand('copy')就可以了。剩下要做的便是先選中元素了。下麵便介紹一下和選中元素相關的selection api
MDN文檔上寫道:Selection
對象表示用戶選擇的文本範圍或插入符號的當前位置。它代表頁面中的文本選區,可能橫跨多個元素。文本選區由用戶拖拽滑鼠經過文字而產生。如果要獲取用於檢查或修改的 Selection
對象,可以調用 window.getSelection()
方法。
這看起來就十分的官方和抽象,簡單的來說Selection 對象所對應的是用戶所選擇的 ranges
(區域),俗稱 拖藍。上圖中的拖藍就是selection對象中的一個區域。
通過getRangeAt方法可以獲取到具體的選中區域
let selection = window.getSelection() //獲取selection對象
let range = selection.getRangeAt(0) //獲取第一個選中的區域
除了獲取選區中的區域之外,我們還可以通過 document.createRange()創建一個新的區域,然後將該區域添加到選區中
<body>
<div id="hello">你好</div>
<div id="yes">是的</div>
</body>
<script>
let selection = window.getSelection() //獲取selection對象
const hello = document.querySelector('#hello')
if(selection.rangeCount > 0){
//如果有已經選中的區域,直接全部去除
selection.removeAllRanges()
}
let range = document.createRange() //創建range
range.selectNode(hello) //range選中hello
selection.addRange(range) //加入到選區中
</script>
效果如下,當代碼執行後,你好這個元素被直接選中
加入區域的api包括range.selectNode和range.selectNodeContents。其中selectNode表示選中整個節點而selectNodeContents表示選中節點中的內容,針對文字的複製需要選中節點的內容,而圖片的複製需要選中節點本身。
用法如下
<body>
<div id="hello">你好</div>
<div id="yes">是的</div>
<button class="btn">點擊複製</button>
</body>
<script>
const yes = document.querySelector('#yes')
const selection = window.getSelection()
const range= document.createRange()
range.selectNode(yes)
range.selectNode(yes)
</script>
2.3複製文字
通過以上的selection api可以完成 創建selection對象-->選中節點內容-->添加到區域-->執行一下copy指令就可以完成複製文字了
<body>
<div id="hello">你好</div>
<div id="yes">是的</div>
<button class="btn">點擊複製</button>
</body>
<script>
const btn = document.querySelector('.btn')
const hello = document.querySelector('#hello')
btn.addEventListener('click', () => {
let range = document.createRange() //創建range
range.selectNodeContents(hello) //range選中hello
let selection = window.getSelection() //獲取selection對象
if (selection.rangeCount > 0) {
//如果有已經選中的區域,直接全部去除
selection.removeAllRanges()
}
selection.addRange(range) //加入到選區中
if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
//先檢測是否支持document.queryCommandSupported和copy指令
//如果都支持直接執行指令
document.execCommand('copy')
//去除選中區域,取消拖藍效果
selection.removeAllRanges()
}
})
</script>
2.4複製圖像
複製圖像的操作是和複製文字基本相同的,只是需要在加入區域時選中整個節點,也就是把selectNodeContents方法換成selectNode
<body>
<div id="hello">你好</div>
<div id="yes">是的</div>
<img src="./test.png" alt="">
<button class="btn">點擊複製</button>
</body>
<script>
const btn = document.querySelector('.btn')
const hello = document.querySelector('#hello')
const img = document.querySelector('img')
btn.addEventListener('click', () => {
let range = document.createRange() //創建range
range.selectNode(img) //range選中圖像節點
let selection = window.getSelection() //獲取selection對象
if (selection.rangeCount > 0) {
//如果有已經選中的區域,直接全部去除
selection.removeAllRanges()
}
selection.addRange(range) //加入到選區中
if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
//先檢測是否支持document.queryCommandSupported和copy指令
//如果都支持直接執行指令
document.execCommand('copy')
//去除選中區域,取消拖藍效果
selection.removeAllRanges()
}
})
</script>
3.clipboard.js
clipboard.js是一個第三方庫,也是使用了前文所講到的document.execCommand('copy')來實現的點擊複製,使用方便,但是只能用於文本的複製。
3.1安裝和引入clipboard.js
使用npm安裝
npm install clipboard --save
安裝後在html文件內引入
<script src="dist/clipboard.min.js"></script>
或者使用CDN引入(這裡只寫了一種CDN引入方式,可以選擇多種不同CDN方,具體請看https://github.com/zenorocha/clipboard.js/wiki/CDN-Providers)
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/clipboard.min.js"></script>
使用import的方式引入
import Clipboard from "clipboard";
3.2基本使用
初始化
直接創建一個ClipboardJS對象,傳的參數可以是選擇器字元串或者是DOM元素或者是DOM元素列表
new ClipboardJS('.btn') // import方式為 new Clipboard('.btn')
實現點擊複製文字功能
初始化完後,可以到要綁定的對應元素下添加data-clipboard-target屬性,屬性值是要複製的元素的選擇器,這裡要複製的元素是 ‘是的’ 那個div,所以屬性值就寫#yes。不進行其他配置時,我們點擊按鈕,觸發點擊事件後,就可以完成複製文字 ‘是的’ 了。
<body>
<div id="hello" >你好</div>
<div id="yes">是的</div>
<button class="btn" data-clipboard-target="#yes">點擊複製</button>
</body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/clipboard.min.js"></script>
<script>
new ClipboardJS('.btn') // import方式為 new Clipboard('.btn')
</script>
點擊後,是的這個元素被選中(拖藍),使用ctrl+v可以完成文字的複製,效果已經達到。
此時有2個問題需要優化
- 複製的內容必須是頁面上的DOM元素,能不能是自己設定的?
- 拖藍的效果不是很好看,如何複製文字不顯示選中效果?
這時就要用到一個新的屬性data-clipboard-text,屬性值就是希望動態複製的內容。對ClipboardJS綁定的元素設置這個屬性就可以動態複製自己設定的內容,此時就不需要再設置data-clipboard-target屬性了(如果同時寫2個屬性,data-clipboard-text優先)。以下代碼是一個寫死的簡單展示,真實使用的時候屬性值要用js設置成需要複製的值。
<body>
<div id="hello" >你好</div>
<div id="yes">是的</div>
<button class="btn" data-clipboard-text="動態設置的內容">點擊複製</button>
</body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/clipboard.min.js"></script>
<script>
new ClipboardJS('.btn') // import方式為 new Clipboard('.btn')
</script>
上圖顯示點擊之後,複製內容成功,這樣沒有選中元素的效果,不會拖藍,交互效果更好的同時又能動態設置內容
3.3更多用法
data-clipboard-action屬性
data-clipboard-action屬性可以決定執行的操作,這個屬性有2個可選值copy或者是cut,預設是copy也就是複製,前面的所有代碼中都沒有出現這個屬性,是直接使用的預設值copy。cut剪切,只能在input和textarea標簽中使用,顯然之前的div標簽是無法使用的。使用方法仍是對ClipboardJS綁定的元素設置這個屬性。
<button class="btn" data-clipboard-text="動態設置的內容" data-clipboard-action="copy">點擊複製</button>
事件處理
事件處理可以讓用戶設置複製或剪切成功或者失敗的回調,事件名分別是success和error。可以通過on在ClipboardJS實例對象身上綁定success和error事件處理的回調。以下示例寫了最簡單alert列印成功和失敗
const clipboard = new ClipboardJS('.btn') // import方式為 new Clipboard('.btn')
clipboard.on('success',function(){
alert('複製成功')
})
clipboard.on('error',function(){
alert('複製失敗')
})
純JS寫法
如果不想改HTML,加入過多的屬性,可以直接使用純JS寫法來初始化ClipboardJS對象構造函數中傳入第二個參數,第二個參數為對象,如下的示例中僅用完成js就完成了動態設置複製內容。設置配置對象的text方法,返回值就是要複製的內容
new ClipboardJS('.btn',{
text: function(){
return '動態複製的內容'
}
})
設置配置對象的target方法,返回值就是要複製的元素
new ClipboardJS('.btn',{
target: function (){
return document.querySelector('#hello')
}
})
經過測試,當html中設置屬性的同時,又在構造函數裡加入配置項,以js構造函數配置項為準(優先順序高)
<body>
<div id="hello">你好</div>
<div id="yes">是的</div>
<button class="btn" data-clipboard-target="#hello">點擊複製</button>
</body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/clipboard.min.js"></script>
<script>
new ClipboardJS('.btn',{
target(){
return document.querySelector('#yes')
}
})
</script>
銷毀對象
如果使用的是單頁應用程式,可能希望更精確地管理DOM的生命周期。可以使用destroy方法銷毀對象
var clipboard = new ClipboardJS('.btn');
clipboard.destroy();
3.4源碼分析
看了之前的api,想瞭解一下這個所謂的簡單的複製庫是如何實現的,於是打開了源碼開始分析一下
源碼地址 https://github.com/zenorocha/clipboard.js
初始化
構造函數裡面傳2個參數,第一個trigger即觸發點擊的元素對象,第二個options配置項。從最簡單的例子來看,只需要傳一個trigger參數就可以實現功能,那就先不管options,直接看與trigger有關的listenClick方法。
listenClick方法調用了一個第三方庫的listen方法綁定了click事件和對應的回調函數this.onClick,在onclick方法中,調用了ClipboardActionDefault方法,並且傳了對應的幾個配置項參數,action container,target,text,這些值都是this.xxx方法,這幾個方法又是在哪定義的呢?
找了一下類內部,定義這些方法的地方是在前文構造函數里的this.resolveOptions方法里
resolveOptions方法里的defaultAction,defaultText等等方法都是類似的,都是調用了一個getAttributeValue方法去獲取html模板上的屬性值
getAttributeValue方法如下,比較簡單
ClipboardActionDefault
上面跳了這麼多方法雖然不難,但是也有點繞,主要還是在乾一件事,那就是通過定義來準備好ClipboardActionDefault這個方法的參數。這時候就要看一下ClipboardActionDefault這個方法在乾什麼。
簡單來看,這個方法主要分4個if判斷,前2個if就是一些條件的判斷,判斷action只能是複製或者剪切,還有就是判斷要複製的目標節點的節點類型和readonly問題等等,此處不展開去研究,有興趣者可以點擊本部分開始處的源碼鏈接下載。
後2個if判斷中的內容如下,分別用於判斷是否有text值和target值,這2個值也是通過本庫的核心屬性data-clipboard-text和data-clipboard-target在html中獲取的(或者在js配置項里獲取)。判斷完後就調用了ClipboardActionCopy或者ClipboardActionCut方法去實現複製或者剪切功能。
ClipboardActionCopy
這個方法就開始進行文本的複製了,首先判斷要複製的目標是普通的字元串(通過data-clipboard-text設置)還是節點(通過data-clipboard-target設置),如果是文本或者不是普通的輸入元素,直接調用fakeCopyAction方法執行複製操作。
fakeCopyAction先創建了虛擬元素,然後把這個元素插入dom里,最後執行選中+複製操作
創建虛擬元素的方法也比較簡單,先通過原生方法createElement創建了一個textarea元素,然後把它隱藏。創建這種輸入類元素的好處就是可以直接去修改它的value,最後一步操作就是把文本text賦值給textarea
創建完虛擬元素就要處理選中問題了,這裡調用了select方法,方法內部根據3種元素類型設置了不同的處理對策,select元素只要focus後賦值就好。輸入元素可以調用原生的select方法來選中元素,而普通元素就需要使用之前講到的selection api去獲取range和添加range了
function select(element) {
var selectedText;
if (element.nodeName === 'SELECT') {
//針對select元素的處理
element.focus();
selectedText = element.value;
}
else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
//選中輸入元素
var isReadOnly = element.hasAttribute('readonly');
if (!isReadOnly) {
element.setAttribute('readonly', '');
}
element.select();
element.setSelectionRange(0, element.value.length);
if (!isReadOnly) {
element.removeAttribute('readonly');
}
selectedText = element.value;
}
else {
//普通元素選中
if (element.hasAttribute('contenteditable')) {
element.focus();
}
var selection = window.getSelection();
var range = document.createRange();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
selectedText = selection.toString();
}
return selectedText;
}
最後的command('copy')也就是對執行複製指令這個方法的簡單封裝,做了一下相容性的處理。
4. navigator.clipboard
前面的document.execCommand和第三方庫clipboard.js都非常的好用,但是他們可能面臨被棄用的風險,那麼該怎麼解決複製粘貼這個問題呢? H5新推出的clipboard api是 處理複製粘貼相關的api,可以很好的解決這個問題。用promise的方式把數據寫入剪貼板,避免了頁面的卡頓。
4.1 複製文字
Clipboard對象
使用Clipboard api時我們不需要手動創建Clipboard對象,而是通過navigator.clipboard來獲取
列印出Clipboard對象後可以看出,這個對象有4個方法,分為兩大類,write和read類。其中與複製相關的是write類表示把數據寫入剪貼板,和粘貼相關的是read類表示從剪貼板裡面讀取數據
writeText方法
Clipboard對象中的writeText方法可以用於複製文字,也是非常簡單易用的一個方法。
參數:傳一個字元串參數,即要複製的內容
返回值: 一個promise對象,如果成功複製則是成功的promise,如果寫入剪貼板失敗(複製失敗)則是失敗的promise
示例如下:先創建了一個clipboard對象,然後直接調用writeText方法複製文字123
navigator.clipboard.writeText('123')
根據之前的html結構,使用Clipboard api完成文字的複製
預設情況下,會為當前的激活的頁面自動授予剪貼板的寫入許可權。出於安全方面考慮,這裡我們還是先主動向用戶請求剪貼板的寫入許可權,如果被授權,就可以調用上面的方法直接完成複製了。
<body>
<div id="hello">你好</div<