你可能還不知道的復(fù)制(Copy) Clipboard API

你可能還不知道的復(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)行處理


1.png

微信截圖粘貼(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)。

參考資料:
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容