视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
关于知乎回答问题编辑框用Ctrl+V粘贴图片是如何实现的详解
2020-11-27 15:15:31 责编:小采
文档
貌似我没有像QQ邮箱之类的装知乎的插件
是用HTML5的新功能实现的吗?
看了@朱利安 的回答,发现我描述的不够清楚
我是用QQ截图之类的工具截的图,然后图片本身是保存在剪切板里的,剪切板中保存的*不是*图片的地址
大家可以自己试一下,用QQ截张图,然后在下面的编辑框里 Ctrl+V 一下

回复内容:

呵呵,刚发现知乎编辑器有这么强的功能,赶紧研究一下,记录如下

  1. 抓包
    截个图,然后粘贴到编辑器,查看 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 应该就是那个图片的二进制数据

打断点
继续追踪就容易多了,只要在这个地方打个断点,然后往编辑器里面粘贴一个截图
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 p 的 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;
 }}

下载本文
显示全文
专题