JavaScript 从剪切版里粘贴图片: HTML5 Clipboard API hacking

layerssss · 2014年02月14日 · 最后由 iaiae 回复于 2016年08月18日 · 27038 次阅读
本帖已被设为精华帖!

最近在做一个在线图片编辑器,觉得如果让用户使用剪切板来粘贴图片(而不是将图片保存到本地,然后再选取文件上传)会很方便。在衡量了客户的浏览器状况之后,我决定使用 HTML5 的 local features 来 hack 一下,最终想达到的效果就是:

  1. 用户在 photoshop 里编辑好原图(可能是从数码相机里传上来的照片)
  2. 然后在 photoshop 里选取一部分图片区域(比如说照片中的人脸)
  3. 然后在编辑器里点 Ctrl + V
  4. 搞掂! photoshop 可以关了(保存还是不保存就随便用户咯)

想一想,这一个工作用剪切板来代替文件上传是会方便很多(不用再单独将编辑结果保存为另外一个文件)

prepare!

所以,这个想法就从一个 demo 开始了。首先,要做好界面上的其他元素的渲染,这个编辑器的界面是用 canvas 来渲染的,所以要先了解一下 canvas 的世界里图片是怎样表示的。

about canvas

MDN 上的这篇文章列出了一个简单的在 canvas 中使用位图的例子,然而如果不太考虑计算效率,我们能拿到粘贴过来的图像的 dataURL 的话,就最方便了,因为 dataURL 就是一个简单的字符串。

about clipboard

然后还要对 “剪切板 “这个系统功能有个简单的认识,不管是 OSX, Windows, 还是 Linux 世界里的一堆桌面环境,现在都对剪切板有了以下共识:

  • 剪切板里可以装 1 个或者多个数据项
  • “粘贴一个图片文件” 和 “粘贴一块图片” 是不同的 (! important):

图片文件是作为一个文件出现的,它里面的数据可能是任意格式的甚至不是图片。而图片,就肯定是表示图片的数据,它不一定有文件名,而它的数据格式也因不同实现而不同。这篇文章介绍的是第二种情况哦(虽然以后有时间我们也可以把第一种情况给解决掉)。

choose your best favorite weapon!

下一步便是从剪切板里读数据啦。我目前的第一开发浏览器是 chrome,所以就从 chrome 开始边调试边开发吧。对于 chrome,开发者们自己写的教程文章很多,于是我便按照这一篇开始了,总结一下,步骤很简单:

  • 拦截元素的paste事件

可以是可编辑元素: input, [contenteditable],也可以是全局元素: window,所以只有在这个元素被激活时(例如正在编辑一个 contenteditable 时),你的粘贴动作才会被拦截

  • event的数据里判断粘贴过来的内容是不是我们想要的内容(我们想要图片)
window.onpaste = (event)-> # it's coffeescript. don't panic!
  for item in event.clipboardData.items # 如果你是用 jQuery 拦截了 paste 事件,你需要用 event.originalEvent 来代替 event 
    if item.type.match /^image\// # item.type 为粘贴过来的内容的 mime-type,如 image/png
      # ...
  • 是图片!调用FileReader拿到它的 dataURL
reader = new FileReader()
reader.onload = (event)=> # 注意这里是异步的哦
  getImageData event.target.result, (data)->
    console.log data
reader.readAsDataURL item.getAsFile()

faithful gecko

关于在 Firefox 里获取剪切板数据的文章相对要少一些,而且我发现刚才针对 chrome 使用的clipboardData.items其实是 chrome 的私有 API,而不是 HTML5 的标准接口,而 Firefox 实现的才是标准的接口。好吧咱来看看标准里面是怎样说的,其实区别不大:

  • 需要从 event.clipboardData.types (一个类数组) 里判断粘贴过来的数据的类型
  • 需要用 event.clipboardData.getData(type) 方法来获取数据

可是我试了一下后发现一个严重的问题: Firefox 不允许粘贴图片 / 在 Firefox 里粘贴图片时获取不到数据

ok, 其实以上那个链接中也说得很明白了,想粘贴图片,hack 吧:ff 里编辑[contenteditable]时,粘贴一个图片的话,ff 会把图片当作图文混排的插图将它转化为一个<img />元素插进去(图片数据被转换成 dataURL 作为这个<img />元素的src属性了),所以该 hack 原理为:

  1. 激活 (focus()) 一个隐藏的div[contenteditable]
  2. 获取paste事件

虽然 ff 不允许粘贴图片,但是paste事件还是能捕获到的,只不过从event.clipboardData获取不到任何数据

  1. setTimeout(..., 1),等待 “下一刻” 图片被插入到[contenteditable]里面
  2. 在这个[contenteditable]里面找到新添加的<img />元素,并拿到它的src属性(就是我们想要的东西——图片数据的 dataURL)。

Internet Explorer, the troll

IE 也没有实现 HTML5 标准里的 API,但是 IE 有个挺简单的私有剪切板 API,而且是从 IE5.5 就已经实现了的。IE 的私有 API 异常简单——直接访问window.clipboardData.getData(type)就可以获取剪切板里的数据了。btw. 大部分同学应该能马上就想到 HTML5 标准 API 不设计成这样的原因,那就是:如果 API 是这样的,那网页便可以随时访问用户剪切板里的内容,用户的隐私便可能会被泄漏。

不过试了一下后发现了 IE11 和 Firefox 有一样的问题: 它不认粘贴来的图片,不过 IE11 的[contenteditable]也支持插入图片并将其转换成<img />,所以用和 ff 同样的 hack 来解决就好了。

old-fashioned Opera

Opera 的情况和 Firefox 类似——实现了 HTML5 标准里的 API,但是不支持粘贴图片。不过比较糟糕的是它的[contenteditable]里也不支持插入图片,所以我也暂时没有找到什么办法让它能支持粘贴图片。

Safari

Safari 的情况也和 Firefox 类似,但是在 Safari 里粘贴至[contenteditable]的图片只会转换为webkit-fake-url://...的形式,而这个数据似乎对我们没有任何用,它只是个本地的临时 url,只能在当前网页的<img />元素里显示图片,用 javascript 也读取不到 (非同源),所以也暂时没办法让它支持粘贴图片了。

ps. webkit 的 bugzilla 里有一个放了很久的 issue,正是针对这个问题而言的,所以看来从[contenteditable]里粘贴暂时看来是没戏了,有其他办法么?

share!

其实俺一开始也到处搜索了一下,希望有人已经做了一个能实现粘贴图片功能的、支持不同浏览器的小 lib(或者 snippets),后来发现确实没有,所以俺就来做这个事儿吧,这样我的编辑器也可以简单地引用它来读取剪切板里的图片:

在这过程中我还找到了一些十分有用的资源 (上文中没有提到的):

碎碎念

其实这个功能对于论坛很有用耶,看:

  1. 截图到剪切板
  2. 编辑完文字后按一下 Ctrl + V
  3. 发帖多么方便,生活多么美好
  4. 求又拍云赞助咱 ruby-china 一个免费 bucket 吧 @_@

已 star:)

知乎的答案编辑器支持粘贴来上传图片。

很酷。

#2 楼 @xinzhi 是的, 我发现虽然大部分一般用户并不知道 “复制/粘贴” 这样一个操作,但是对于功能性产品,比如:知乎、Github、论坛,如果天天都需要使用 “截图 - 上传” 这个流程的话,学习并接受这么个操作还是可行的。

so so so cool!!

@poshboytl 迅速来看看,目前 Fengche.co 只支持了 Chrome 的粘贴,原来还有这么多 hack。 @layerssss Safari 有办法不?

@yedingding 俺也刚发现 safari 漏了,这就去试试

@yedingding safari 木有办法,最多只能检测到图片的 长和宽 (通过Image),却拿不到图片里的数据。

微博在 chrome 浏览器也是支持这样上传图片滴

赞,Github 很久前就支持 chrome 的 Clipboard paste,去年一段时候我也很想做这样一个 library,然后就没有然后了。

奇怪在我的 window 系统下 clipboardData.items 啥也没有,contentediable=true 也粘贴不了。等于无解了。mac 下倒是可以~

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册