在用戶執行粘貼操作的時候,js能夠獲得剪切板的內容,本文討論一下這個問題。
目前只有支持獲取剪切板中的圖片數據。還好需要這個功能的產品目前只支持和,一些的新特性是可以盡情使用了,還是能夠覆蓋到大部分用戶的。所以本文只討論如何使用和如何阻止,原理大概了解了,再研究其他瀏覽器相關的問題就容易多了。
paste事件
可以用js給頁面中的元素綁定paste事件的方法,當用戶鼠標在該元素上或者該元素處于focus狀態,綁定到paste事件的方法就運行了。
綁定的元素不一定是input,普通的div也是可以綁定的,如果是給綁定了,就相當于全局了,任何時候的粘貼操作都會觸發。
事件對象獲取事件對象
先寫一下事件綁定的代碼
pasteEle.addEventListener("paste", function (e){
// 不支持獲取圖片的瀏覽器 直接return
if ( !(e.clipboardData && e.clipboardData.items) ) {
return;
}
});
粘貼事件提供了一個的屬性,如果該屬性有items屬性,那么就可以查看items中是否有圖片類型的數據了。有該屬性,沒有。
介紹
介紹一下對象,它實際上是一個類型的對象,是拖動產生的一個對象,但實際上粘貼事件也是它。
的屬性介紹
屬性類型說明
默認是 none
默認是
files
粘貼操作為空List
items
剪切板中的各項數據
types
Array
剪切板中的數據類型 該屬性在下比較混亂
items介紹
items是一個對象,自然里面都是類型的數據了。
屬性
items的有兩個屬性kind和type
屬性說明
kind
一般為或者file
type
具體的數據類型,例如具體是哪種類型字符串或者哪種類型的文件,即MIME-Type
方法方法參數說明
空
如果kind是file,可以用該方法獲取到文件
回調函數
如果kind是,可以用該方法獲取到字符串,字符串需要用回調函數得到網頁復制內容到剪貼板,回調函數的第一個參數就是剪切板中的字符串
在原型上還有一些其他方法,不過在處理剪切板操作的時候一般用不到了。
types介紹
一般types中常見的值有text/plain、text/html、Files。
值說明
text/plain
普通字符串
text/html
帶有樣式的html
Files
文件(例如剪切板中的數據)
簡單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就是獲取到的文件
}
}
});
注意如果是類型的數據,可能針對具體是text/plain、text/html進行分別的處理。
問題來了
一切看似都很順利,如果用戶粘貼了圖片,通過上面的方法我們是可以獲取到,可以對圖片進行上傳等操作了。
首先要說一下js通過剪切板能獲取到的圖片是怎么來的,它必須是用QQ截圖或者系統截圖功能截下來的圖片,或者是網頁上某個圖片單擊右鍵復制圖片等。
但是如果用戶復制Mac的中的一個圖片文件,實際上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;
}
這么多的判斷條件,基本可以確定通過剪切板過來的是粘貼的文件。我剛才測試了的,不會有這個問題,當然也不能通過復制文件的方法得到任何文件。
問題又來了
當我打算寫這篇博客的時候,開發版已經升級到了49,上面的Bug突然消失了,囧。
所以上面的Hack應該加上版本限制了。
var ua = window.navigator.userAgent;
ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49
應該在上面的Hack再加上這兩個判斷,即是Mac下的版本以下就要。
探究過程走的一點彎路
由于公司IM系統正在遷移到V2消息系統,而且現有的文件類庫沒有辦法滿足業務需求,要自己封裝一個文件上傳庫。
然后副總找到產品經理,說新版怎么不支持Excel的粘貼,臨時排期一天修復這個問題,當時是這樣解決的,如果items長度是1并且是文件類型(單純粘貼一個文件),則上傳,如果items長度是4且第4個是文件類型(經過測試是Excel的粘貼結果),則上傳。
當時擔心由于用戶各種誤操作,粘貼了不該粘貼的東西,文件上傳錯誤,用了這種白名單機制去過濾,但是萬一以后有比Excel粘貼得到的數據更其他的類型,就需要單獨寫代碼兼容,所以,現在改成了如果判斷是有Bug的情況,直接,屬于黑名單機制,這樣以后再發現黑名單的情況,再添加。
可以拿來就用的代碼
// 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);