你可能還不知道的復(fù)制(Copy) -- Js
<font color=red>!!! PC & 客戶端<font color=#000>
PS:你有沒(méi)有被復(fù)制、粘貼圖片或文字而感到煩惱, sparrow 來(lái)解決你終身疾病,趕快報(bào)名吧, 人員有限 饑渴難耐啊 Bro!
其實(shí)今天的主題就是要圍繞著js里面的“復(fù)制”這一專業(yè)名詞來(lái)概述全文。
「引言」PM: 我們這期來(lái)闡述一下1.6客戶端需求, 巴拉巴拉... 在輸入框里面粘貼圖片、文字、文件功能,下期我們還會(huì)增加“復(fù)制”圖片、文字功能, 再送給你一個(gè)拖拽文件圖片文字到輸入框功能... 我懵 PM: 哦 對(duì)了 還要兼容微信等其他粘貼圖片的情況.... mmpmmpmmp
就是這個(gè)不到一炷香的時(shí)間,引發(fā)了思考, 咋做咋做咋做??粘貼倒是還有點(diǎn)眉目,paste事件嘛 再根據(jù)里面的類型做處理就好了, 拖拽也還行drag而已了 我在送給你一個(gè)進(jìn)來(lái)時(shí)候背景變色, 但是 復(fù)制怎么做? 文字好像用上古年代的execCommand應(yīng)該就是沒(méi)多大問(wèn)題吧,但是鬼圖片 還要兼容微信的粘貼功能 甚至還有wps 瀏覽器各種情況下的兼容, 這咋搞 ?? 聽(tīng)我潺潺道來(lái) (三天兩夜幾百根頭發(fā)換來(lái)的)
Overview
- 粘貼圖片格式、規(guī)則
- 微信聊天圖片粘貼(Rule)
- 微信截圖粘貼(Rule)
- 拖拽
- 復(fù)制
- 復(fù)制文字
- 復(fù)制圖片
粘貼圖片格式、規(guī)則
粘貼圖片所監(jiān)聽(tīng)的event里會(huì)有不同的類型格式,在某個(gè)軟件里粘貼出來(lái),可能要遵循人家內(nèi)部復(fù)制圖片所謂的規(guī)則,目前我遇見(jiàn)的只有微信這個(gè) 78(路人的眼光)。
微信聊天圖片粘貼(Rule)
這就是微信聊天頁(yè)面里面的圖片粘貼, (隨便在微信找個(gè)帶有圖片的聊天窗口,然后右鍵復(fù)制)然后... 擼代碼啊
// 這里我主要是應(yīng)用富文本來(lái)實(shí)現(xiàn)的輸入框正常操作
<div
class={["el-textarea", this.changeInputBackground ? 'background' : '']}
ref={'messageContent'}
spellcheck="false"
vScrollbar
contentEditable="true"
onInput={e => this.messageInput(e)}
on-keydown={(e) => this.keydown(e)}
placeholder=""
on-focus={e => this.keepLastIndex(e.target)}
on-drop={e => this.drop(e)}
on-paste={e => this.paste(e)}
on-blur={e => this.textareaBlur(e)}
on-dragenter={e => this.dragEnter(e)}
on-dragleave={e => this.dragLeave(e)}
domPropsInnerHTML={this.vmodelMessage}
></div>
/**
* 粘貼事件
*/
paste(event) {
event.preventDefault()
let * = this.$refs.messageContent // 輸入框內(nèi)容 * 隨便寫的
var clipboardData = event.clipboardData || window.clipboardData
const items = clipboardData.items
const text = clipboardData.getData('text/plain') // 獲取文本
let apptext = document.createTextNode(text)
let i = 0, tl = items.length;
let imagesblob = [] // 這里寫了幾種類型 bolb加密、 base64 、 formdata 這個(gè)可以上傳時(shí)候和服務(wù)端做協(xié)商
let imagesbase64 = []
// var fd = new FormData(document.forms[0]);
for (; i < tl;) {
if (items[i].kind == 'file') {
let file = items[i].getAsFile();
if (file.type.indexOf('image') > -1) {
let blob = URL.createObjectURL(file) // 創(chuàng)建bolb
let el = document.createElement('img')
el.title = file.name
el.src = blob
el.dataset.name = file.name
if (file.path.length == 0 && tl == 1) { // 剪切板情況
var reader = new FileReader();
reader.onload = function (event) {
el.dataset.path = event.target.result
el.dataset.type = '2' // 2是沒(méi)有path時(shí)候發(fā)送
}
reader.readAsDataURL(file);
imagesblob.push(el)
} else {
if (file.path.length !== 0) {
el.dataset.path = file.path
el.dataset.type = '1' // 1是正常發(fā)送
imagesblob.push(el)
}
}
// var reader = new FileReader();
// reader.onload = function (event) {
// var base64_str = event.target.result;
// imagesbase64.push(base64_str);
// }
// reader.readAsDataURL(file);
// fd.append('file' + i, file)
}
}
i++
}
*.append(apptext) // 添加文本
if (imagesblob.length > 0) {
for (let j = 0; j < imagesblob.length; j++) {
*.append(imagesblob[j]) // 添加圖片
}
}
this.keepLastIndex(*) // 光標(biāo)滯后
},
其實(shí)這個(gè)還好 但是你永遠(yuǎn)不知道會(huì)有什么魔鬼出現(xiàn) 比如微信輸入框里面復(fù)制的時(shí)候會(huì)出現(xiàn)兩個(gè)圖片, 然后第一張圖解析出來(lái)之后沒(méi)有path, 第二張會(huì)有path, 這樣一來(lái)你肯定會(huì)判斷沒(méi)有path那張圖片,然后進(jìn)行處理

微信截圖粘貼(Rule)
還有一種情況, 是屏幕截圖的時(shí)候, 你會(huì)發(fā)現(xiàn)這個(gè)圖片使用二進(jìn)制存在這個(gè)剪切板里面的 沒(méi)有路徑儲(chǔ)存, 他就是一串亂碼,然后 會(huì)發(fā)現(xiàn)有沖突, 跟剛才的情況有很大的沖突... 上邊代碼解決了都 我也忘記咋寫的了(好幾個(gè)月前寫的了)
拖拽
獻(xiàn)上一份垃圾代碼
drop(event) {
event.preventDefault()
let imgTypeFn = file => file.type.indexOf('image') > -1
let items = event.dataTransfer.files // 獲取拖拽的所有文件
// event.dataTransfer.getData('text/plain') //獲取拖拽進(jìn)來(lái)的文字
let * = this.$refs.messageContent
const text = event.dataTransfer.getData('text/plain')
let apptext = document.createTextNode(text)
let i = 0, tl = items.length;
let imagesblob = []
for (; i < tl;) {
if (imgTypeFn(items[i])) {
let blob = URL.createObjectURL(items[i])
let el = document.createElement('img')
el.title = items[i].name
el.src = blob
el.dataset.name = items[i].name
el.dataset.path = items[i].path
imagesblob.push(el)
}
i++
}
*.append(apptext)
if (imagesblob.length > 0) {
for (let j = 0; j < imagesblob.length; j++) {
*.append(imagesblob[j])
}
}
this.keepLastIndex(*)
},
好了 主角上場(chǎng)了
復(fù)制
其實(shí)在此之前,還是查了一下網(wǎng)上的各種資料, 也有很多很好的工具庫(kù)實(shí)現(xiàn)了這個(gè)功能;比如 clipboard.js; 還有原生方法 document.execCommand('copy') 、event.clipboardData (copy) (paste) 、Navigator.clipboard 前面那兩個(gè)我們應(yīng)該不是很陌生,今天主要說(shuō)后面那個(gè)新增的。PS:其實(shí)我很想更細(xì)化些搞出點(diǎn)東西,但是時(shí)間節(jié)點(diǎn)不允許我這樣做,so... 搓搓手湊活看吧
額... 我這里直接說(shuō)今天的重點(diǎn)吧 Navigator.clipboard 剪貼板 這是新版本的剪切、復(fù)制、粘貼
剪貼板API
瀏覽器允許 JavaScript 腳本讀寫剪貼板,自動(dòng)復(fù)制或粘貼內(nèi)容。
一般來(lái)說(shuō),腳本不應(yīng)該改動(dòng)用戶的剪貼板,以免不符合用戶的預(yù)期。但是,有些時(shí)候這樣做確實(shí)能夠帶來(lái)方便,比如"一鍵復(fù)制"功能,用戶點(diǎn)擊一下按鈕,指定的內(nèi)容就自動(dòng)進(jìn)入剪貼板。
Document.execCommand() 方法 (阮一峰老師的日志)
Document.execCommand()是操作剪貼板的傳統(tǒng)方法,
各種瀏覽器都支持。
它支持復(fù)制、剪切和粘貼這三個(gè)操作。
-
document.execCommand('copy')(復(fù)制)
// 這種選擇方式就要?jiǎng)?chuàng)建input去賦值內(nèi)容然后select 然后復(fù)制 const inputElement = document.querySelector('#input'); inputElement.select(); document.execCommand('copy'); document.execCommand('cut')(剪切)
-
document.execCommand('paste')(粘貼)
// 同樣原理 const pasteText = document.querySelector('#output'); pasteText.focus(); document.execCommand('paste'); 缺點(diǎn)
Document.execCommand()方法雖然方便,但是有一些缺點(diǎn)。
首先,它只能將選中的內(nèi)容復(fù)制到剪貼板,無(wú)法向剪貼板任意寫入內(nèi)容。
其次,它是同步操作,如果復(fù)制/粘貼大量數(shù)據(jù),頁(yè)面會(huì)出現(xiàn)卡頓。有些瀏覽器還會(huì)跳出提示框,要求用戶許可,這時(shí)在用戶做出選擇前,頁(yè)面會(huì)失去響應(yīng)。
為了解決這些問(wèn)題,瀏覽器廠商提出了異步的 Clipboard API。
Clipboard API
Clipboard API 是下一代的剪貼板操作方法,比傳統(tǒng)的document.execCommand()方法更強(qiáng)大、更合理;
它的所有操作都是異步的,返回 Promise 對(duì)象,不會(huì)造成頁(yè)面卡頓。而且,它可以將任意內(nèi)容(比如圖片)放入剪貼板;
navigator.clipboard屬性返回 Clipboard 對(duì)象,所有操作都通過(guò)這個(gè)對(duì)象進(jìn)行;
const clipboardObj = navigator.clipboard;
如果navigator.clipboard屬性返回undefined,就說(shuō)明當(dāng)前瀏覽器不支持這個(gè) API。
Clipboard 對(duì)象提供的方法:
-
read()
從剪貼板讀取數(shù)據(jù)(比如圖片),返回一個(gè) Promise 對(duì)象。When the data has been retrieved, the promise is resolved with a DataTransfer object that provides the data。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(URL.createObjectURL(blob)); } } } catch (err) { console.error(err.name, err.message); } } -
readText()
從操作系統(tǒng)讀取文本;returns a Promise which is resolved with a DOMString containing the clipboard's text once it's available。document.body.addEventListener( 'click', async (e) => { const text = await navigator.clipboard.readText(); console.log(text); } ) -
write()
寫入任意數(shù)據(jù)至操作系統(tǒng)剪貼板。This asynchronous operation signals that it's finished by resolving the returned Promise。try { const imgURL = 'https://dummyimage.com/300.png'; const data = await fetch(imgURL); const blob = await data.blob(); await navigator.clipboard.write([ new ClipboardItem({ [blob.type]: blob }) ]); console.log('Image copied.'); } catch (err) { console.error(err.name, err.message); } -
writeText()
寫入文本至操作系統(tǒng)剪貼板。returning a Promise which is resolved once the text is fully copied into the clipboard。document.body.addEventListener( 'click', async (e) => { await navigator.clipboard.writeText('Yo') } )
!!! 上面在使用write寫入圖片的時(shí)候, 可能會(huì)出現(xiàn)點(diǎn)小問(wèn)題, 比如blob格式不符合然后就很難達(dá)到我們的預(yù)期, 就很煩是吧
比如:Cannot read properties of undefined (reading 'substring') 、 Failed to read or decode Blob for clipboard item type image/png. 這種錯(cuò)誤的來(lái)源就是圖片格式不正確或者是 在解析的為blob格式時(shí)候出現(xiàn)了問(wèn)題。 額.. 那就手動(dòng)解析一下吧
先把圖片解析成base64在解析成blob在傳入ClipboardItem在傳入write復(fù)制。 嗯 就這樣
//先解析成base64
function imageBase64(img) {
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, img.width, img.height);
var dataURL = canvas.toDataURL("image/png");
return dataURL;
}
// 再轉(zhuǎn)成blod // 注意base64里面的特殊符號(hào) 在atob的時(shí)候不能識(shí)別“,”
function base64ToBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = window.atob(b64Data);
// var byteCharacters = b64Data;
//該atob函數(shù)將base64編碼的字符串解碼為一個(gè)新字符串,其中包含二進(jìn)制數(shù)據(jù)每個(gè)字節(jié)的字符。
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
//通過(guò)使用.charCodeAt字符串中每個(gè)字符的方法應(yīng)用它來(lái)創(chuàng)建一個(gè)字節(jié)值數(shù)組。
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
//將此字節(jié)值數(shù)組轉(zhuǎn)換為實(shí)際類型的字節(jié)數(shù)組,方法是將其傳遞給Uint8Array構(gòu)造函數(shù)。
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
console.log(byteArrays)
//創(chuàng)建一個(gè)blob:包含此數(shù)據(jù)的URL,并將其顯示給用戶。
var blob = new Blob(byteArrays, { type: contentType });
return blob;
}
// 最后復(fù)制
copy_img.onclick = async _ => {
let base64 = imageBase64(img)
let blob = base64ToBlob(base64.replace('data:image/png;base64,', ''), 'image/png')
clipboardObj.write([
new ClipboardItem({
'image/png': blob
})
])
}
// Nice
Event.clipboardData
- Event.clipboardData.setData(type, data):修改剪貼板數(shù)據(jù),需要指定數(shù)據(jù)類型。
- Event.clipboardData.getData(type):獲取剪貼板數(shù)據(jù),需要指定數(shù)據(jù)類型。
- Event.clipboardData.clearData([type]):清除剪貼板數(shù)據(jù),可以指定數(shù)據(jù)類型。如果不指定類型,將清除所有類型的數(shù)據(jù)。
- Event.clipboardData.items:一個(gè)類似數(shù)組的對(duì)象,包含了所有剪貼項(xiàng),不過(guò)通常只有一個(gè)剪貼項(xiàng)。