js 獲取剪切板內容,控制圖片粘貼。

目前只有Chrome支持獲取剪切板中的圖片數據。還好需要這個功能的產品目前只支持Chrome和Safari,一些Chrome的新特性是可以盡情使用了,還是能夠覆蓋到大部分用戶的。所以本文只討論Chrome如何使用和如何阻止Safari,原理大概瞭解了,再研究其他瀏覽器相關的問題就容易多了。

paste事件
可以用js給頁面中的元素綁定paste事件的方法,當用戶鼠標在該元素上或者該元素處於focus狀態,綁定到paste事件的方法就運行了。

綁定的元素不一定是input,普通的div也是可以綁定的,如果是給document綁定了,就相當於全局了,任何時候的粘貼操作都會觸發。

事件對象
獲取事件對象
先寫一下事件綁定的代碼

pasteEle.addEventListener("paste", function (e){
    if ( !(e.clipboardData && e.clipboardData.items) ) {
        return;
    }
});

粘貼事件提供了一個clipboardData的屬性,如果該屬性有items屬性,那麼就可以查看items中是否有圖片類型的數據了。Chrome有該屬性,Safari沒有。

clipboardData介紹
介紹一下clipboardData對象,它實際上是一個DataTransfer類型的對象,DataTransfer 是拖動產生的一個對象,但實際上粘貼事件也是它。

參考
MDN.
MDN past.

demo

pasteEle.addEventListener("paste", function (e){
    if ( !(e.clipboardData && e.clipboardData.items) ) {
        return ;
    }

    for (var i = 0, len = e.clipboardData.items.length; i < len; i++) {
        var item = e.clipboardData.items[i];

        if (item.kind === "string") {
            item.getAsString(function (str) {
                // str 是獲取到的字符串
            })
        } else if (item.kind === "file") {
            var pasteFile = item.getAsFile();
            // pasteFile就是獲取到的文件
        }
    }
});

注意如果是string類型的數據,可能針對具體是text/plain、text/html進行分別的處理。

問題來了
一切看似都很順利,如果用戶粘貼了圖片,通過上面的方法我們是可以獲取到,可以對圖片進行上傳等操作了。

首先要說一下js通過剪切板能獲取到的圖片是怎麼來的,它必須是用QQ截圖或者系統截圖功能截下來的圖片,或者是網頁上某個圖片單擊右鍵複製圖片等。

但是如果用戶複製Mac的Finder中的一個圖片文件,實際上js是沒有辦法獲取到這個圖片的。但是js確實會獲得一個圖片類型的文件,這個圖片實際上圖片在電腦中的圖標標識,說的比較抽象,直接上圖。

如果複製的是JPEG圖片,粘貼過來的卻是Mac上的文件縮略圖,後面依次是PNG、GIF、ZIP、DMG、Mac目錄的文件縮略圖。

很明顯,這不是我們期待得到的粘貼的結果,我們期待得到文件,但實際上卻得到該文件在操作系統上的縮略圖。

不過粘貼事件帶來的數據還有一個字符串,就是該文件的名字,所以可以用下面的方法Hack掉。

    var cbd = e.clipboardData;
    if(cbd.items && cbd.items.length === 2 && cbd.items[0].kind === "string" && cbd.items[1].kind === "file" &&
            cbd.types && cbd.types.length === 2 && cbd.types[0] === "text/plain" && cbd.types[1] === "Files"){
        return;
    }

這麼多的判斷條件,基本可以確定通過剪切板過來的是粘貼的文件。我剛纔測試了Windows的Chrome,不會有這個問題,當然也不能通過複製文件的方法得到任何文件。

問題又來了
當我打算寫這篇博客的時候,Chrome開發版已經升級到了49,上面的Bug突然消失了,囧。
所以上面的Hack應該加上版本限制了。

var ua = window.navigator.userAgent;
ua.match(/Macintosh/i) && Number(ua.match(/Chrome/(\d{2})/i)[1]) < 49
應該在上面的Hack再加上這兩個判斷,即是Mac下的Chrome49版本以下就要return。

探究過程走的一點彎路
由於公司IM系統正在遷移到V2消息系統,而且現有的文件類庫沒有辦法滿足業務需求,要自己封裝一個文件上傳庫。

然後副總找到產品經理,說新版怎麼不支持Excel的粘貼,臨時排期一天修復這個問題,當時是這樣解決的,如果items長度是1並且是文件類型(單純粘貼一個文件),則上傳,如果items長度是4且第4個是文件類型(經過測試是Excel的粘貼結果),則上傳。

當時擔心由於用戶各種誤操作,粘貼了不該粘貼的東西,文件上傳錯誤,用了這種白名單機制去過濾,但是萬一以後有比Excel粘貼得到的數據更其他的類型,就需要單獨寫代碼兼容,所以,現在改成了如果判斷是有Bug的情況,直接return,屬於黑名單機制,這樣以後再發現黑名單的情況,再添加。

// demo 程序將粘貼事件綁定到 document 上
document.addEventListener("paste", function (e) {
    var cbd = e.clipboardData;
    var ua = window.navigator.userAgent;

    // 如果是 Safari 直接 return
    if ( !(e.clipboardData && e.clipboardData.items) ) {
        return;
    }
    
    // Mac平臺下Chrome49版本以下 複製Finder中的文件的Bug Hack掉
    if(cbd.items && cbd.items.length === 2 && cbd.items[0].kind === "string" && cbd.items[1].kind === "file" &&
        cbd.types && cbd.types.length === 2 && cbd.types[0] === "text/plain" && cbd.types[1] === "Files" &&
        ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49){
        return;
    }

    for(var i = 0; i < cbd.items.length; i++) {
        var item = cbd.items[i];
        if(item.kind == "file"){
            var blob = item.getAsFile();
            if (blob.size === 0) {
                return;
            }
            // blob 就是從剪切板獲得的文件 可以進行上傳或其他操作
        }
    }
}, false);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章