目前只有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 是拖動產生的一個對象,但實際上粘貼事件也是它。
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);