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

你可能還不知道的復制(Copy) -- Js

<font color=red>!!! PC & 客戶端<font color=#000>

PS:你有沒有被復制、粘貼圖片或文字而感到煩惱, sparrow 來解決你終身疾病,趕快報名吧, 人員有限 饑渴難耐啊 Bro!

其實今天的主題就是要圍繞著js里面的“復制”這一專業(yè)名詞來概述全文。

「引言」PM: 我們這期來闡述一下1.6客戶端需求, 巴拉巴拉... 在輸入框里面粘貼圖片、文字、文件功能,下期我們還會增加“復制”圖片、文字功能, 再送給你一個拖拽文件圖片文字到輸入框功能... 我懵 PM: 哦 對了 還要兼容微信等其他粘貼圖片的情況.... mmpmmpmmp

就是這個不到一炷香的時間,引發(fā)了思考, 咋做咋做咋做??粘貼倒是還有點眉目,paste事件嘛 再根據(jù)里面的類型做處理就好了, 拖拽也還行drag而已了 我在送給你一個進來時候背景變色, 但是 復制怎么做? 文字好像用上古年代的execCommand應該就是沒多大問題吧,但是鬼圖片 還要兼容微信的粘貼功能 甚至還有wps 瀏覽器各種情況下的兼容, 這咋搞 ?? 聽我潺潺道來 (三天兩夜幾百根頭發(fā)換來的)

Overview

  • 粘貼圖片格式、規(guī)則
    • 微信聊天圖片粘貼(Rule)
    • 微信截圖粘貼(Rule)
  • 拖拽
  • 復制
    • 復制文字
    • 復制圖片

粘貼圖片格式、規(guī)則

粘貼圖片所監(jiān)聽的event里會有不同的類型格式,在某個軟件里粘貼出來,可能要遵循人家內部復制圖片所謂的規(guī)則,目前我遇見的只有微信這個 78(路人的眼光)。

微信聊天圖片粘貼(Rule)

這就是微信聊天頁面里面的圖片粘貼, (隨便在微信找個帶有圖片的聊天窗口,然后右鍵復制)然后... 擼代碼啊

// 這里我主要是應用富文本來實現(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 // 輸入框內容 * 隨便寫的 
        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 這個可以上傳時候和服務端做協(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是沒有path時候發(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(*) // 光標滯后
    },

其實這個還好 但是你永遠不知道會有什么魔鬼出現(xiàn) 比如微信輸入框里面復制的時候會出現(xiàn)兩個圖片, 然后第一張圖解析出來之后沒有path, 第二張會有path, 這樣一來你肯定會判斷沒有path那張圖片,然后進行處理


1.png

微信截圖粘貼(Rule)

還有一種情況, 是屏幕截圖的時候, 你會發(fā)現(xiàn)這個圖片使用二進制存在這個剪切板里面的 沒有路徑儲存, 他就是一串亂碼,然后 會發(fā)現(xiàn)有沖突, 跟剛才的情況有很大的沖突... 上邊代碼解決了都 我也忘記咋寫的了(好幾個月前寫的了)

拖拽

獻上一份垃圾代碼

    drop(event) {
        event.preventDefault()
        let imgTypeFn = file => file.type.indexOf('image') > -1
        let items = event.dataTransfer.files // 獲取拖拽的所有文件
        // event.dataTransfer.getData('text/plain')  //獲取拖拽進來的文字
        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(*)
    },

好了 主角上場了

復制

其實在此之前,還是查了一下網(wǎng)上的各種資料, 也有很多很好的工具庫實現(xiàn)了這個功能;比如 clipboard.js; 還有原生方法 document.execCommand('copy') 、event.clipboardData (copy) (paste) 、Navigator.clipboard 前面那兩個我們應該不是很陌生,今天主要說后面那個新增的。PS:其實我很想更細化些搞出點東西,但是時間節(jié)點不允許我這樣做,so... 搓搓手湊活看吧
額... 我這里直接說今天的重點吧 Navigator.clipboard 剪貼板 這是新版本的剪切、復制、粘貼
剪貼板API
瀏覽器允許 JavaScript 腳本讀寫剪貼板,自動復制或粘貼內容。
一般來說,腳本不應該改動用戶的剪貼板,以免不符合用戶的預期。但是,有些時候這樣做確實能夠帶來方便,比如"一鍵復制"功能,用戶點擊一下按鈕,指定的內容就自動進入剪貼板。

Document.execCommand() 方法 (阮一峰老師的日志)

 Document.execCommand()是操作剪貼板的傳統(tǒng)方法,
 各種瀏覽器都支持。
 它支持復制、剪切和粘貼這三個操作。
  • document.execCommand('copy')(復制)

      // 這種選擇方式就要創(chuàng)建input去賦值內容然后select 然后復制
      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');
    
  • 缺點
    Document.execCommand()方法雖然方便,但是有一些缺點。
    首先,它只能將選中的內容復制到剪貼板,無法向剪貼板任意寫入內容。
    其次,它是同步操作,如果復制/粘貼大量數(shù)據(jù),頁面會出現(xiàn)卡頓。有些瀏覽器還會跳出提示框,要求用戶許可,這時在用戶做出選擇前,頁面會失去響應。

為了解決這些問題,瀏覽器廠商提出了異步的 Clipboard API。

Clipboard API

Clipboard API 是下一代的剪貼板操作方法,比傳統(tǒng)的document.execCommand()方法更強大、更合理;
它的所有操作都是異步的,返回 Promise 對象,不會造成頁面卡頓。而且,它可以將任意內容(比如圖片)放入剪貼板;
navigator.clipboard屬性返回 Clipboard 對象,所有操作都通過這個對象進行;

const clipboardObj = navigator.clipboard;
如果navigator.clipboard屬性返回undefined,就說明當前瀏覽器不支持這個 API。
Clipboard 對象提供的方法:

  • read()
    從剪貼板讀取數(shù)據(jù)(比如圖片),返回一個 Promise 對象。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寫入圖片的時候, 可能會出現(xiàn)點小問題, 比如blob格式不符合然后就很難達到我們的預期, 就很煩是吧
比如:Cannot read properties of undefined (reading 'substring')Failed to read or decode Blob for clipboard item type image/png. 這種錯誤的來源就是圖片格式不正確或者是 在解析的為blob格式時候出現(xiàn)了問題。 額.. 那就手動解析一下吧
先把圖片解析成base64在解析成blob在傳入ClipboardItem在傳入write復制。 嗯 就這樣

    //先解析成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;
    }
    // 再轉成blod // 注意base64里面的特殊符號 在atob的時候不能識別“,”
    function base64ToBlob(b64Data, contentType, sliceSize) {
        contentType = contentType || '';
        sliceSize = sliceSize || 512;
        var byteCharacters = window.atob(b64Data);
        // var byteCharacters  = b64Data;
        //該atob函數(shù)將base64編碼的字符串解碼為一個新字符串,其中包含二進制數(shù)據(jù)每個字節(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);
            //通過使用.charCodeAt字符串中每個字符的方法應用它來創(chuàng)建一個字節(jié)值數(shù)組。
            for (var i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
            }
            //將此字節(jié)值數(shù)組轉換為實際類型的字節(jié)數(shù)組,方法是將其傳遞給Uint8Array構造函數(shù)。
            var byteArray = new Uint8Array(byteNumbers);
            byteArrays.push(byteArray);
        }
        console.log(byteArrays)
        //創(chuàng)建一個blob:包含此數(shù)據(jù)的URL,并將其顯示給用戶。
        var blob = new Blob(byteArrays, { type: contentType });
        return blob;
    }

    // 最后復制
    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:一個類似數(shù)組的對象,包含了所有剪貼項,不過通常只有一個剪貼項。

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容