知乎回答問題編輯框用 Ctrl+V 粘貼圖片是如何實現的?
呵呵,剛發現知乎編輯器有這麼強的功能,趕緊研究一下,記錄如下
- 抓包截個圖,然後粘貼到編輯器,查看 HTTP 包,發現有對http://upload.zhihu.com/upload 的請求request 的格式是multipart/form-data; 圖片的內容作為request body 的一部分一起傳了過去這裡大概就能推測出基本原理了:監聽粘貼 → 獲取粘貼內容 → 將內容上傳
- 搜索代碼
在 rich_text_editor.js 裡面搜索 /upload 字樣,搜到了這麼一段
this.Vz = "http://upload." + Ak.Sl + ":" + location.port + "/upload";
估計是設置屬性,那麼再搜 .Vz 看看哪裡用到了,於是看到
function zE(a, b) {
var c = new FormData;
c.append("via", "xhr2");
c.append("upload_file", b);
var d;
d = $.ajaxSettings.xhr();
d.withCredentials = i;
var f = $.ajax({url: a.Vz,data: c,processData: l,contentType: l,xhr: function() {
return d
},type: "POST"}).done(function(a) {
啊,找到了,這裡應該就是發送圖片數據的地方,而且用了 FormData 這個 HTML5 特性
簡單說就是 ajax 以前只能向伺服器發送文本,而 HTML5 提供的 XMLHttpRequest Level2 現在支持發送二進位數據了,這裡的 c.append("upload_file", b) 裡面 b 應該就是那個圖片的二進位數據- 打斷點繼續追蹤就容易多了,只要在這個地方打個斷點,然後往編輯器裡面粘貼一個截圖Chrome 調試工具的 Call Stack 就會告訴你,程序的上一步在哪裡
看一看 a 對象的屬性基本可以斷定它是一個 Event 對象,而且這裡的這段 function 就是對粘貼事件的處理,為了驗證,搜索一下 .rw 就會看到這樣一段
a.f().addEventListener("paste", u(this.rw, this));
確定推斷無誤,可以看到上面的處理函數中,通過 a.clipboardData 就能取到剪貼板中的數據,並且可以通過 clipboardData.types 來判斷數據是不是圖片。
這麼高級的 API 是哪裡來的呢?搜一下就知道了 Clipboard API and events可以看到這個 API 屬於 W3C 的標準(當然還是草案階段),但是不屬於 HTML5
另外代碼中的重點是這麼一段
c.type.indexOf("image") (zE(b, c.getAsFile()), a.preventDefault())
zE 就是上面的那個 ajax 發送函數,而通過 c.getAsFile() 可以從剪貼板中獲取二進位的數據
- 結論通過 Clipboard API 可以在用戶粘貼時獲知粘貼的內容,包括內容的格式(是否為圖片),內容的二進位數據等等通過 XMLHttpRequest Level2 可以實現將二進位數據以 ajax 的方式發送到伺服器,即實現了上傳功能當然以上都需要瀏覽器的支持,估計IE下就悲劇了最後,我現在迫切期待新浪微博的發布框能支持這個功能
這個和 HTML5 沒有太大關係,網頁瀏覽器很早就有這個標準了,不同瀏覽器下的實現介面略有區別。
粘貼(包括富文本、圖片等各種格式的內容)是利用了 contentEditable div 的 onPaste 事件。知乎的 JavaScript 源代碼經過了混淆和壓縮,非人類可閱讀的,所以我就不扒他的代碼了。
當在編輯器上執行粘貼的時候,onPaste 事件被觸發,同時有一個事件參數 event,包含 clipboardData 的屬性。簡單來說,代碼邏輯可能是這樣的:
function onPasteEvent (e)
{
if (e e.clipboardData e.clipboardData.getData)
{
if (/image/.test(e.clipboardData.types))
{
//粘貼圖片
var imageContent = e.clipboardData.getData("image/xxxx");
//檢測圖片來源
//如果是最原始的 data,比如 QQ 截圖、Word 里複製所產生,直接把 data 上傳
//這一部分可能用了是 HTML5 中 HTTP_CONTENT_DISPOSITION 上傳機制
//除了 HTML5,非顯式的 input[type="file"] 應該是無法上傳文件的
//如果是 file,上傳到知乎伺服器
//如果是外部網站 URL,後台 curl get 轉移到知乎伺服器
//最後生成一個知乎的 URL,作為 img 標籤插入到 contentEditable div 中
}
else if (/text/plain/.test(e.clipboardData.types)) {
//粘貼簡單文本 ....
}
else
{
//....
}if (e.preventDefault)
{
e.stopPropagation();
e.preventDefault();
}
return false;
}
}
在@葛亮@Dion 的啟發下,繼續挖掘了一下。收穫如下:
在IE和Firefox下,利用html5全局屬性 contenteditable 來實現根據uri粘貼一幅圖像。
在Chrome下,利用特有的 clipboardData.items介面 和 getAsFile方法實現增強的粘貼圖像功能(其實是從剪貼板上傳的功能)。細說如下:
一個HTML5全局屬性:
contenteditable 使相應元素在頁面呈現 可編輯/不可編輯 狀態see HTML 5 全局 contenteditable 屬性&&
一個推測(尚未嚴格驗證,請知者指教):
複製一幅圖片到系統粘貼板中 會有以下內容在剪貼板中生成(可能缺少某些項):來源(類似 src ),標題(類似title),內容(可以是二進位的)根據這三項的有無和可訪問性,我們考慮以下三種剪貼板圖像(獲取方式下詳):type 1. public_uri/local_img(hvae src, title, content; local can access; )type 2. private_uri(have src, title, content; local can not access; )type 3. print_screen(have content; no src, title; local can access; )對於前面提到的&&試驗之。
代碼如下:&
&
&
&
&
&test contenteditable&
&paste img here&
&
將上面的代碼存儲為一個html文件,然後用不同的瀏覽器打開。
分別用如下方式獲取type1 type2 type3 三張圖片:type 1. 在你的知乎頭像上點擊滑鼠右鍵,複製圖片;type 2. 用QQ截圖,然後在截圖上點擊滑鼠右鍵,複製;type 3. press PrtSc/SysRq;然後用Ctrl+V的方式將剪貼板內容粘貼到頁面的DIV裡面。
看到如下現象:IE8:
type 1, 將一個 IMG標籤寫入DIV,並設置相應的屬性,如 src, title。頁面上會顯示出這幅圖像,真實數據是從src指定的uri讀取的。
type 2, 同1,但是由於沒有URI的訪問許可權,頁面上會顯示為一個無法顯示的圖像。 type 3, 由於沒有 src, titile等內容,不會生成IMG元素,所以粘貼沒有任何效果 (注意:這只是IE的行為,其他瀏覽器可能有不同的行為,下詳)FireFox24:
type 1, 同IE
type 2, 同IE type 3, 雖然沒有src, title。但是有content在。firefox還是會生成一個IMG標籤。 標籤的title等屬性為空,src屬性為圖片的數據內容,如 &Chrome31:
type 1, 同IE type 2, 同IEtype 3, 同IE
再來看一下知乎編輯框的粘貼情況,同樣用上面三種粘貼板圖像來測試:IE: 同上(1 ok, with origion url; 2 not ok, with origion url; 3 not ok, no response)FF: 同上(1 ok, with origion url; 2 not ok, with origion url; 3 ok, with data in src)Chrome: type 1 (ok, with new url )!type 2 (ok, with new url )!! (圖中是一張qq截圖)
type 3 (ok, with new url )!!
全都是new uri,並且是指向知乎的伺服器的,說明圖片已經從剪貼板上傳到伺服器上了。看來知乎的編輯框針對chrome 有特殊處理,或者說chrome提供了對粘貼板圖片進行特殊處理的途徑。參考了 Paste Image From Clipboard With Javascript and MooTools後,得到如下信息:chrome提供了訪問粘貼板中二進位數據的介面(clipboardData.items),並提供了將其轉換為文件的方法(getAsFile)。demo 如下(裡面用到的圖像請自行替換):&
&
&
&
&
window.onload=function() {
function paste_img(e) {
if ( e.clipboardData.items ) {
// google-chrome
alert("support clipboardData.items(chrome ...)");
ele = e.clipboardData.items
for (var i = 0; i &< ele.length; ++i) {
if ( ele[i].kind == "file" ele[i].type.indexOf("image/") !== -1 ) {
var blob = ele[i].getAsFile();
window.URL = window.URL || window.webkitURL;
var blobUrl = window.URL.createObjectURL(blob);
console.log(blobUrl);
var new_img= document.createElement("img");
new_img.setAttribute("src", blobUrl);
var new_img_intro = document.createElement("p");
new_img_intro.innerHTML = "the pasted img url(open it in new tab): &
&" + blobUrl + "&";
document.getElementById("editable").appendChild(new_img);
document.getElementById("editable").appendChild(new_img_intro);
}
}
} else {
alert("non-chrome");
}
}
document.getElementById("editable").onpaste=function(){paste_img(event);return false;};
}
&
&
&
&
test image paste in browser&
&&
copy the following img, then paste it into the area below&
&&
&
this is an editable div area&
&paste the image into here.&
&