知乎回答問題編輯框用 Ctrl+V 粘貼圖片是如何實現的?


呵呵,剛發現知乎編輯器有這麼強的功能,趕緊研究一下,記錄如下

  1. 抓包

    截個圖,然後粘貼到編輯器,查看 HTTP 包,發現有對http://upload.zhihu.com/upload 的請求

    request 的格式是multipart/form-data; 圖片的內容作為request body 的一部分一起傳了過去

    這裡大概就能推測出基本原理了:監聽粘貼 → 獲取粘貼內容 → 將內容上傳
  2. 搜索代碼

    在 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 應該就是那個圖片的二進位數據

  3. 打斷點

    繼續追蹤就容易多了,只要在這個地方打個斷點,然後往編輯器裡面粘貼一個截圖

    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() 可以從剪貼板中獲取二進位的數據

  4. 結論

    通過 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; )

對於前面提到的&& 元素而言:

不同瀏覽器會以不同的行為響應onpaste事件。

試驗之。

代碼如下:

&
&
&test contenteditable&
&

&
&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, 同IE

type 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 如下(裡面用到的圖像請自行替換):

&
&
&test chrome paste image&
& DIV#editable {
width: 400px;
height: 300px;
border: 1px dashed blue;
}
&
&