1.功能需求
實(shí)習(xí)工作中,遇到一個(gè)需求,需要完成點(diǎn)擊復(fù)制的功能,當(dāng)文字過長的時(shí)候,讓用戶手拖再ctrl+c這種方式體驗(yàn)就不是很好了,如果可以點(diǎn)擊一下直接復(fù)制就是一種不錯(cuò)的優(yōu)化用戶體驗(yàn)的方式。
經(jīng)過查閱文檔,網(wǎng)絡(luò)上完成這個(gè)功能大多使用兩大類方法
第一種是以document.execCommand() 方法為主,無論是手寫還是使用clipboard.js插件都是依賴的這個(gè)方法,但是在MDN 文檔中已經(jīng)顯示過時(shí)了。
第二種是用了navigator.clipboard的方法,避免了過時(shí)問題,但是在復(fù)制圖片的時(shí)候會有一定的瀏覽器兼容性問題
?2.document.execCommand('copy')?
這個(gè)方法其實(shí)就是在模擬用戶選擇元素然后右鍵復(fù)制的動作。盡管MDN已經(jīng)顯示這個(gè)方法過時(shí)了,但是僅針對copy這個(gè)指令,大部分主流瀏覽器都可以支持,所以這個(gè)方法仍然可以作為一種實(shí)現(xiàn)問題的方案。
2.1 基本用法
根據(jù)MDN文檔學(xué)習(xí)本方法的傳參和返回值
語法
bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)
這個(gè)方法可以傳3個(gè)參數(shù),并且會返回一個(gè)布爾值
返回值
先從返回值開始,返回值相對比較簡單,如果返回的值是false就表示瀏覽器不支持使用這個(gè)操作,反之瀏覽器支持該操作就返回true。
雖然這個(gè)返回值看似可以用來提前判斷瀏覽器兼容性,但是文檔中不推薦在調(diào)用一個(gè)命令前,嘗試使用返回值去校驗(yàn)瀏覽器的兼容性
參數(shù)值
參數(shù)一共可以傳3個(gè),但是使用復(fù)制命令的時(shí)候只需要傳第一個(gè)參數(shù)就可以。這里簡單介紹一下3個(gè)參數(shù)
- aCommandName:一個(gè)字符串類型的參數(shù),是命令的名稱,比如復(fù)制用到的copy,剪切用到的cut
- aShowDefaultUI:一個(gè)布爾類型的參數(shù),表示是否展示用戶界面,一般為false,Mozilla 沒有實(shí)現(xiàn)
- aValueArgument:一些命令(例如 insertImage)需要額外的參數(shù)(insertImage 需要提供插入 image 的 url),默認(rèn)為 null。
簡單舉例
以本文主要講的復(fù)制命令為例子:document.execCommand('copy')
指令兼容性問題
前文講到,MDN不推薦在調(diào)用一個(gè)命令前,嘗試使用返回值去校驗(yàn)瀏覽器的兼容性,那么就需要用另外的方法去檢測瀏覽器是否支持某個(gè)指令,瀏覽器為我們提供了一個(gè)方法叫document.queryCommandSupported(),使用這個(gè)方法可以檢測瀏覽器是否支持某個(gè)指令,這個(gè)方法比較簡單,只有1個(gè)參數(shù),參數(shù)就傳指令字符串,方法的返回值是一個(gè)布爾值表示當(dāng)前瀏覽器是否支持這個(gè)指令。
舉例如下:
?文章來源地址http://www.zghlxwxcb.cn/news/detail-679237.html
if(document.queryCommandSupported && document.queryCommandSupported('copy')){
//先檢測是否支持document.queryCommandSupported和copy指令
//如果都支持直接執(zhí)行指令
document.execCommand('copy')
}
?
MDN文檔中提到,document.queryCommandSupported也被棄用了,但是為了兼容性依然保留可用,當(dāng)我們使用document.execCommand的時(shí)候仍然可以用document.queryCommandSupported來檢測是否支持。同時(shí),它的瀏覽器兼容性也是比較好的,大部分主流瀏覽器都支持。
?
2.2?Selection Api
復(fù)制文本這個(gè)操作對比復(fù)制圖片是相對比較簡單的,一共包含2大步
一是選中要復(fù)制的元素
二是執(zhí)行復(fù)制指令。
執(zhí)行復(fù)制指令在前面的基本語法里已經(jīng)講到了,直接調(diào)用document.execCommand('copy')就可以了。剩下要做的便是先選中元素了。下面便介紹一下和選中元素相關(guān)的selection api
MDN文檔上寫道:Selection
?對象表示用戶選擇的文本范圍或插入符號的當(dāng)前位置。它代表頁面中的文本選區(qū),可能橫跨多個(gè)元素。文本選區(qū)由用戶拖拽鼠標(biāo)經(jīng)過文字而產(chǎn)生。如果要獲取用于檢查或修改的?Selection
?對象,可以調(diào)用?window.getSelection()
?方法。
這看起來就十分的官方和抽象,簡單的來說Selection 對象所對應(yīng)的是用戶所選擇的?ranges
?(區(qū)域),俗稱?拖藍(lán)。上圖中的拖藍(lán)就是selection對象中的一個(gè)區(qū)域。
通過getRangeAt方法可以獲取到具體的選中區(qū)域
let selection = window.getSelection() //獲取selection對象
let range = selection.getRangeAt(0) //獲取第一個(gè)選中的區(qū)域
?除了獲取選區(qū)中的區(qū)域之外,我們還可以通過?document.createRange()創(chuàng)建一個(gè)新的區(qū)域,然后將該區(qū)域添加到選區(qū)中
<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){
//如果有已經(jīng)選中的區(qū)域,直接全部去除
selection.removeAllRanges()
}
let range = document.createRange() //創(chuàng)建range
range.selectNode(hello) //range選中hello
selection.addRange(range) //加入到選區(qū)中
</script>
?
效果如下,當(dāng)代碼執(zhí)行后,你好這個(gè)元素被直接選中
加入?yún)^(qū)域的api包括range.selectNode和range.selectNodeContents。其中selectNode表示選中整個(gè)節(jié)點(diǎn)而selectNodeContents表示選中節(jié)點(diǎn)中的內(nèi)容,針對文字的復(fù)制需要選中節(jié)點(diǎn)的內(nèi)容,而圖片的復(fù)制需要選中節(jié)點(diǎn)本身。
用法如下
<body>
<div id="hello">你好</div>
<div id="yes">是的</div>
<button class="btn">點(diǎn)擊復(fù)制</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復(fù)制文字
通過以上的selection api可以完成 創(chuàng)建selection對象-->選中節(jié)點(diǎn)內(nèi)容-->添加到區(qū)域-->執(zhí)行一下copy指令就可以完成復(fù)制文字了
<body>
<div id="hello">你好</div>
<div id="yes">是的</div>
<button class="btn">點(diǎn)擊復(fù)制</button>
</body>
<script>
const btn = document.querySelector('.btn')
const hello = document.querySelector('#hello')
btn.addEventListener('click', () => {
let range = document.createRange() //創(chuàng)建range
range.selectNodeContents(hello) //range選中hello
let selection = window.getSelection() //獲取selection對象
if (selection.rangeCount > 0) {
//如果有已經(jīng)選中的區(qū)域,直接全部去除
selection.removeAllRanges()
}
selection.addRange(range) //加入到選區(qū)中
if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
//先檢測是否支持document.queryCommandSupported和copy指令
//如果都支持直接執(zhí)行指令
document.execCommand('copy')
//去除選中區(qū)域,取消拖藍(lán)效果
selection.removeAllRanges()
}
})
</script>
?
2.4復(fù)制圖像
復(fù)制圖像的操作是和復(fù)制文字基本相同的,只是需要在加入?yún)^(qū)域時(shí)選中整個(gè)節(jié)點(diǎn),也就是把selectNodeContents方法換成selectNode
<body>
<div id="hello">你好</div>
<div id="yes">是的</div>
<img src="./test.png" alt="">
<button class="btn">點(diǎn)擊復(fù)制</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() //創(chuàng)建range
range.selectNode(img) //range選中圖像節(jié)點(diǎn)
let selection = window.getSelection() //獲取selection對象
if (selection.rangeCount > 0) {
//如果有已經(jīng)選中的區(qū)域,直接全部去除
selection.removeAllRanges()
}
selection.addRange(range) //加入到選區(qū)中
if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
//先檢測是否支持document.queryCommandSupported和copy指令
//如果都支持直接執(zhí)行指令
document.execCommand('copy')
//去除選中區(qū)域,取消拖藍(lán)效果
selection.removeAllRanges()
}
})
</script>
?
3.clipboard.js
clipboard.js是一個(gè)第三方庫,也是使用了前文所講到的document.execCommand('copy')來實(shí)現(xiàn)的點(diǎn)擊復(fù)制,使用方便,但是只能用于文本的復(fù)制。
3.1安裝和引入clipboard.js
使用npm安裝
npm install clipboard --save
安裝后在html文件內(nèi)引入
<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/clipboard@2.0.11/dist/clipboard.min.js"></script>
使用import的方式引入
import Clipboard from "clipboard";
3.2基本使用
初始化
直接創(chuàng)建一個(gè)ClipboardJS對象,傳的參數(shù)可以是選擇器字符串或者是DOM元素或者是DOM元素列表
new ClipboardJS('.btn') // import方式為 new Clipboard('.btn')
?
實(shí)現(xiàn)點(diǎn)擊復(fù)制文字功能
初始化完后,可以到要綁定的對應(yīng)元素下添加data-clipboard-target屬性,屬性值是要復(fù)制的元素的選擇器,這里要復(fù)制的元素是 ‘是的’ 那個(gè)div,所以屬性值就寫#yes。不進(jìn)行其他配置時(shí),我們點(diǎn)擊按鈕,觸發(fā)點(diǎn)擊事件后,就可以完成復(fù)制文字 ‘是的’ 了。
<body>
<div id="hello" >你好</div>
<div id="yes">是的</div>
<button class="btn" data-clipboard-target="#yes">點(diǎn)擊復(fù)制</button>
</body>
<script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js"></script>
<script>
new ClipboardJS('.btn') // import方式為 new Clipboard('.btn')
</script>
?點(diǎn)擊后,是的這個(gè)元素被選中(拖藍(lán)),使用ctrl+v可以完成文字的復(fù)制,效果已經(jīng)達(dá)到。
此時(shí)有2個(gè)問題需要優(yōu)化
- 復(fù)制的內(nèi)容必須是頁面上的DOM元素,能不能是自己設(shè)定的?
- 拖藍(lán)的效果不是很好看,如何復(fù)制文字不顯示選中效果?
這時(shí)就要用到一個(gè)新的屬性data-clipboard-text,屬性值就是希望動態(tài)復(fù)制的內(nèi)容。對ClipboardJS綁定的元素設(shè)置這個(gè)屬性就可以動態(tài)復(fù)制自己設(shè)定的內(nèi)容,此時(shí)就不需要再設(shè)置data-clipboard-target屬性了(如果同時(shí)寫2個(gè)屬性,data-clipboard-text優(yōu)先)。以下代碼是一個(gè)寫死的簡單展示,真實(shí)使用的時(shí)候?qū)傩灾狄胘s設(shè)置成需要復(fù)制的值。
<body>
<div id="hello" >你好</div>
<div id="yes">是的</div>
<button class="btn" data-clipboard-text="動態(tài)設(shè)置的內(nèi)容">點(diǎn)擊復(fù)制</button>
</body>
<script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js"></script>
<script>
new ClipboardJS('.btn') // import方式為 new Clipboard('.btn')
</script>
?上圖顯示點(diǎn)擊之后,復(fù)制內(nèi)容成功,這樣沒有選中元素的效果,不會拖藍(lán),交互效果更好的同時(shí)又能動態(tài)設(shè)置內(nèi)容
3.3更多用法
data-clipboard-action屬性
data-clipboard-action屬性可以決定執(zhí)行的操作,這個(gè)屬性有2個(gè)可選值copy或者是cut,默認(rèn)是copy也就是復(fù)制,前面的所有代碼中都沒有出現(xiàn)這個(gè)屬性,是直接使用的默認(rèn)值copy。cut剪切,只能在input和textarea標(biāo)簽中使用,顯然之前的div標(biāo)簽是無法使用的。使用方法仍是對ClipboardJS綁定的元素設(shè)置這個(gè)屬性。
<button class="btn" data-clipboard-text="動態(tài)設(shè)置的內(nèi)容" data-clipboard-action="copy">點(diǎn)擊復(fù)制</button>
事件處理
事件處理可以讓用戶設(shè)置復(fù)制或剪切成功或者失敗的回調(diào),事件名分別是success和error??梢酝ㄟ^on在ClipboardJS實(shí)例對象身上綁定success和error事件處理的回調(diào)。以下示例寫了最簡單alert打印成功和失敗
const clipboard = new ClipboardJS('.btn') // import方式為 new Clipboard('.btn')
clipboard.on('success',function(){
alert('復(fù)制成功')
})
clipboard.on('error',function(){
alert('復(fù)制失敗')
})
純JS寫法
如果不想改HTML,加入過多的屬性,可以直接使用純JS寫法來初始化ClipboardJS對象構(gòu)造函數(shù)中傳入第二個(gè)參數(shù),第二個(gè)參數(shù)為對象,如下的示例中僅用完成js就完成了動態(tài)設(shè)置復(fù)制內(nèi)容。設(shè)置配置對象的text方法,返回值就是要復(fù)制的內(nèi)容
new ClipboardJS('.btn',{
text: function(){
return '動態(tài)復(fù)制的內(nèi)容'
}
})
設(shè)置配置對象的target方法,返回值就是要復(fù)制的元素
new ClipboardJS('.btn',{
target: function (){
return document.querySelector('#hello')
}
})
經(jīng)過測試,當(dāng)html中設(shè)置屬性的同時(shí),又在構(gòu)造函數(shù)里加入配置項(xiàng),以js構(gòu)造函數(shù)配置項(xiàng)為準(zhǔn)(優(yōu)先級高)
<body>
<div id="hello">你好</div>
<div id="yes">是的</div>
<button class="btn" data-clipboard-target="#hello">點(diǎn)擊復(fù)制</button>
</body>
<script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js"></script>
<script>
new ClipboardJS('.btn',{
target(){
return document.querySelector('#yes')
}
})
</script>
銷毀對象
如果使用的是單頁應(yīng)用程序,可能希望更精確地管理DOM的生命周期??梢允褂胐estroy方法銷毀對象
var clipboard = new ClipboardJS('.btn');
clipboard.destroy();
3.4源碼分析
看了之前的api,想了解一下這個(gè)所謂的簡單的復(fù)制庫是如何實(shí)現(xiàn)的,于是打開了源碼開始分析一下
源碼地址?https://github.com/zenorocha/clipboard.js
初始化
構(gòu)造函數(shù)里面?zhèn)?個(gè)參數(shù),第一個(gè)trigger即觸發(fā)點(diǎn)擊的元素對象,第二個(gè)options配置項(xiàng)。從最簡單的例子來看,只需要傳一個(gè)trigger參數(shù)就可以實(shí)現(xiàn)功能,那就先不管options,直接看與trigger有關(guān)的listenClick方法。
listenClick方法調(diào)用了一個(gè)第三方庫的listen方法綁定了click事件和對應(yīng)的回調(diào)函數(shù)this.onClick,在onclick方法中,調(diào)用了ClipboardActionDefault方法,并且傳了對應(yīng)的幾個(gè)配置項(xiàng)參數(shù),action container,target,text,這些值都是this.xxx方法,這幾個(gè)方法又是在哪定義的呢?
?
找了一下類內(nèi)部,定義這些方法的地方是在前文構(gòu)造函數(shù)里的this.resolveOptions方法里
resolveOptions方法里的defaultAction,defaultText等等方法都是類似的,都是調(diào)用了一個(gè)getAttributeValue方法去獲取html模板上的屬性值
getAttributeValue方法如下,比較簡單?
ClipboardActionDefault
上面跳了這么多方法雖然不難,但是也有點(diǎn)繞,主要還是在干一件事,那就是通過定義來準(zhǔn)備好ClipboardActionDefault這個(gè)方法的參數(shù)。這時(shí)候就要看一下ClipboardActionDefault這個(gè)方法在干什么。
簡單來看,這個(gè)方法主要分4個(gè)if判斷,前2個(gè)if就是一些條件的判斷,判斷action只能是復(fù)制或者剪切,還有就是判斷要復(fù)制的目標(biāo)節(jié)點(diǎn)的節(jié)點(diǎn)類型和readonly問題等等,此處不展開去研究,有興趣者可以點(diǎn)擊本部分開始處的源碼鏈接下載。
后2個(gè)if判斷中的內(nèi)容如下,分別用于判斷是否有text值和target值,這2個(gè)值也是通過本庫的核心屬性data-clipboard-text和data-clipboard-target在html中獲取的(或者在js配置項(xiàng)里獲?。?。判斷完后就調(diào)用了ClipboardActionCopy或者ClipboardActionCut方法去實(shí)現(xiàn)復(fù)制或者剪切功能。
ClipboardActionCopy
這個(gè)方法就開始進(jìn)行文本的復(fù)制了,首先判斷要復(fù)制的目標(biāo)是普通的字符串(通過data-clipboard-text設(shè)置)還是節(jié)點(diǎn)(通過data-clipboard-target設(shè)置),如果是文本或者不是普通的輸入元素,直接調(diào)用fakeCopyAction方法執(zhí)行復(fù)制操作。
?
?fakeCopyAction先創(chuàng)建了虛擬元素,然后把這個(gè)元素插入dom里,最后執(zhí)行選中+復(fù)制操作
創(chuàng)建虛擬元素的方法也比較簡單,先通過原生方法createElement創(chuàng)建了一個(gè)textarea元素,然后把它隱藏。創(chuàng)建這種輸入類元素的好處就是可以直接去修改它的value,最后一步操作就是把文本text賦值給textarea
?創(chuàng)建完虛擬元素就要處理選中問題了,這里調(diào)用了select方法,方法內(nèi)部根據(jù)3種元素類型設(shè)置了不同的處理對策,select元素只要focus后賦值就好。輸入元素可以調(diào)用原生的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')也就是對執(zhí)行復(fù)制指令這個(gè)方法的簡單封裝,做了一下兼容性的處理。
4.?navigator.clipboard
前面的document.execCommand和第三方庫clipboard.js都非常的好用,但是他們可能面臨被棄用的風(fēng)險(xiǎn),那么該怎么解決復(fù)制粘貼這個(gè)問題呢??H5新推出的clipboard api是 處理復(fù)制粘貼相關(guān)的api,可以很好的解決這個(gè)問題。用promise的方式把數(shù)據(jù)寫入剪貼板,避免了頁面的卡頓。
4.1 復(fù)制文字
Clipboard對象
使用Clipboard api時(shí)我們不需要手動創(chuàng)建Clipboard對象,而是通過navigator.clipboard來獲取
打印出Clipboard對象后可以看出,這個(gè)對象有4個(gè)方法,分為兩大類,write和read類。其中與復(fù)制相關(guān)的是write類表示把數(shù)據(jù)寫入剪貼板,和粘貼相關(guān)的是read類表示從剪貼板里面讀取數(shù)據(jù)
?writeText方法
Clipboard對象中的writeText方法可以用于復(fù)制文字,也是非常簡單易用的一個(gè)方法。
參數(shù):傳一個(gè)字符串參數(shù),即要復(fù)制的內(nèi)容
返回值: 一個(gè)promise對象,如果成功復(fù)制則是成功的promise,如果寫入剪貼板失敗(復(fù)制失?。﹦t是失敗的promise
示例如下:先創(chuàng)建了一個(gè)clipboard對象,然后直接調(diào)用writeText方法復(fù)制文字123
navigator.clipboard.writeText('123')
根據(jù)之前的html結(jié)構(gòu),使用Clipboard api完成文字的復(fù)制
默認(rèn)情況下,會為當(dāng)前的激活的頁面自動授予剪貼板的寫入權(quán)限。出于安全方面考慮,這里我們還是先主動向用戶請求剪貼板的寫入權(quán)限,如果被授權(quán),就可以調(diào)用上面的方法直接完成復(fù)制了。
<body>
<div id="hello">你好</div>
<div id="yes">是的</div>
<img src="./test.png" alt="">
<button class="btn">點(diǎn)擊復(fù)制</button>
</body>
<script>
const btn = document.querySelector('.btn')
const hello = document.querySelector('#hello')
const img = document.querySelector('img')
btn.addEventListener('click', async () => {
const { state } = await navigator.permissions.query({
// 出于安全方面考慮,這里我們還是主動向用戶請求剪貼板的寫入權(quán)限
name: "clipboard-write",
});
if (state == 'granted') {
navigator.clipboard.writeText(hello.innerHTML)
}
})
</script>
?
4.2 復(fù)制圖像
write方法
write方法除了支持文本數(shù)據(jù)之外,還支持將圖像數(shù)據(jù)寫入到剪貼板,調(diào)用該方法后會返回一個(gè) Promise 對象。
以下是簡單的使用案例,先通過 Blob API 創(chuàng)建 Blob 對象,然后使用該 Blob 對象來構(gòu)造 ClipboardItem 對象,最后再通過 write 方法把數(shù)據(jù)寫入到剪貼板,復(fù)制了文字(當(dāng)前頁面的地址)
<button onclick="copyPageUrl()">拷貝當(dāng)前頁面地址</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("頁面地址已經(jīng)被拷貝到剪貼板中");
} catch (err) {
console.error("頁面地址拷貝失敗: ", err);
}
}
</script>
復(fù)制圖片案例
了解了write的基本用法,那使用Clipboard對象復(fù)制圖片也有辦法了,只要先把圖像變成Blob對象,然后構(gòu)造?ClipboardItem 對象,最后再調(diào)用write方法就好。
可是,如何把一個(gè)img標(biāo)簽里的圖片轉(zhuǎn)換成Blob對象呢?
首先從Blob對象的構(gòu)造函數(shù)開始,MDN文檔寫了Blob構(gòu)造函數(shù)所需要的參數(shù)
var aBlob = new Blob( array, options );
array 是一個(gè)由ArrayBuffer, ArrayBufferView, Blob, DOMString 等對象構(gòu)成的 Array ,或者其他類似對象的混合體,它將會被放進(jìn) Blob。DOMStrings 會被編碼為 UTF-8。
options 是一個(gè)可選的BlobPropertyBag字典,它可能會指定如下兩個(gè)屬性:
- type,默認(rèn)值為 "",它代表了將會被放入到 blob 中的數(shù)組內(nèi)容的 MIME 類型。
- endings,默認(rèn)值為"transparent",用于指定包含行結(jié)束符\n的字符串如何被寫入。它是以下兩個(gè)值中的一個(gè):"native",代表行結(jié)束符會被更改為適合宿主操作系統(tǒng)文件系統(tǒng)的換行符,或者 "transparent",代表會保持 blob 中保存的結(jié)束符不變
?
根據(jù)文檔中顯示,我們需要先準(zhǔn)備一個(gè)對應(yīng)的數(shù)組,然后才能構(gòu)造Blob對象,也就是要把圖片轉(zhuǎn)成二進(jìn)制數(shù)據(jù)。
分步驟實(shí)現(xiàn)把圖片轉(zhuǎn)換成Blob
- 把img圖像畫在canvas畫布上
- 調(diào)用canvas的toDataURL方法,獲取圖片的base64編碼
- 調(diào)用atob方法,把base64編碼的數(shù)據(jù)轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)
- 根據(jù)轉(zhuǎn)換后的二進(jìn)制數(shù)據(jù),創(chuàng)建一個(gè)視圖,此視圖將把緩沖內(nèi)的數(shù)據(jù)格式化為一個(gè) 8 位無符號整數(shù)數(shù)組,也就是獲得了一個(gè)ArrayBufferView數(shù)組(關(guān)于ArrayBuffer和ArrayBufferView的內(nèi)容詳細(xì)可查閱?JavaScript 類型化數(shù)組)
?
下方代碼完成了基本的功能實(shí)現(xiàn):
?
?微信輸入框顯示,可以完成復(fù)制
這個(gè)方法在瀏覽器兼容性上仍存在一些問題,比如火狐可能就不支持ClipboardItem對象,此時(shí)只能用前文寫的document.execCommand方法了。
<body>
<div id="hello">你好</div>
<div id="yes">是的</div>
<img style="width: 400px; height: 200px" src="./test.png" alt="">
<button class="btn">點(diǎn)擊復(fù)制</button>
</body>
<script>
const btn = document.querySelector('.btn')
const hello = document.querySelector('#hello')
const img = document.querySelector('img')
btn.addEventListener('click', async () => {
const {
state
} = await navigator.permissions.query({
// 出于安全方面考慮,這里我們還是主動向用戶請求剪貼板的寫入權(quán)限
name: "clipboard-write",
});
if (state == 'granted') {
//創(chuàng)建canvas對象
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const image = new Image()
image.src = img.src
image.onload = async () => {
canvas.width = image.width;
canvas.height = image.height;
context.drawImage(image, 0, 0, image.width, image.height); // img圖片轉(zhuǎn)成canvas
const imageDataUrl = canvas.toDataURL() //通過canvas獲取base64編碼
const binary = atob(imageDataUrl.split(',')[1]); // base64編碼轉(zhuǎn)二進(jìn)制數(shù)據(jù)
const array = [];
for (let i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
//二進(jìn)制數(shù)據(jù)轉(zhuǎn)Blob對象
const blob = new Blob([new Uint8Array(array)], {
type: 'image/png'
});
// 判斷瀏覽器是否有ClipboardItem對象,有些瀏覽器不支持本方法
if (typeof ClipboardItem !== 'undefined') {
//把blob數(shù)據(jù)寫入剪貼板
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
])
}
}
}
})
</script>
?文章來源:http://www.zghlxwxcb.cn/news/detail-679237.html
?
到了這里,關(guān)于前端實(shí)現(xiàn)復(fù)制文字和圖片,原來這么簡單!的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!