最近在做一个在线图片编辑器,觉得如果让用户使用剪切板来粘贴图片(而不是将图片保存到本地,然后再选取文件上传)会很方便。在衡量了客户的浏览器状况之后,我决定使用 HTML5 的 local features 来 hack 一下,最终想达到的效果就是:
想一想,这一个工作用剪切板来代替文件上传是会方便很多(不用再单独将编辑结果保存为另外一个文件)
所以,这个想法就从一个 demo 开始了。首先,要做好界面上的其他元素的渲染,这个编辑器的界面是用 canvas 来渲染的,所以要先了解一下 canvas 的世界里图片是怎样表示的。
MDN 上的这篇文章列出了一个简单的在 canvas 中使用位图的例子,然而如果不太考虑计算效率,我们能拿到粘贴过来的图像的 dataURL 的话,就最方便了,因为 dataURL 就是一个简单的字符串。
然后还要对“剪切板“这个系统功能有个简单的认识,不管是 OSX, Windows, 还是 Linux 世界里的一堆桌面环境,现在都对剪切板有了以下共识:
图片文件是作为一个文件出现的,它里面的数据可能是任意格式的甚至不是图片。而图片,就肯定是表示图片的数据,它不一定有文件名,而它的数据格式也因不同实现而不同。这篇文章介绍的是第二种情况哦(虽然以后有时间我们也可以把第一种情况给解决掉)。
下一步便是从剪切板里读数据啦。我目前的第一开发浏览器是 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
拿到它的 dataURLreader = new FileReader()
reader.onload = (event)=> # 注意这里是异步的哦
getImageData event.target.result, (data)->
console.log data
reader.readAsDataURL item.getAsFile()
关于在 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 原理为:
focus()
) 一个隐藏的div[contenteditable]
paste
事件虽然 ff 不允许粘贴图片,但是paste
事件还是能捕获到的,只不过从event.clipboardData
获取不到任何数据
setTimeout(..., 1)
,等待“下一刻”图片被插入到[contenteditable]
里面[contenteditable]
里面找到新添加的<img />
元素,并拿到它的src
属性(就是我们想要的东西——图片数据的 dataURL)。IE 也没有实现 HTML5 标准里的 API,但是 IE 有个挺简单的私有剪切板 API,而且是从 IE5.5 就已经实现了的。IE 的私有 API 异常简单——直接访问window.clipboardData.getData(type)
就可以获取剪切板里的数据了。btw. 大部分同学应该能马上就想到 HTML5 标准 API 不设计成这样的原因,那就是:如果 API 是这样的,那网页便可以随时访问用户剪切板里的内容,用户的隐私便可能会被泄漏。
不过试了一下后发现了 IE11 和 Firefox 有一样的问题:它不认粘贴来的图片,不过 IE11 的[contenteditable]
也支持插入图片并将其转换成<img />
,所以用和 ff 同样的 hack 来解决就好了。
Opera 的情况和 Firefox 类似——实现了 HTML5 标准里的 API,但是不支持粘贴图片。不过比较糟糕的是它的[contenteditable]
里也不支持插入图片,所以我也暂时没有找到什么办法让它能支持粘贴图片。
Safari 的情况也和 Firefox 类似,但是在 Safari 里粘贴至[contenteditable]
的图片只会转换为webkit-fake-url://...
的形式,而这个数据似乎对我们没有任何用,它只是个本地的临时 url,只能在当前网页的<img />
元素里显示图片,用 javascript 也读取不到 (非同源),所以也暂时没办法让它支持粘贴图片了。
ps. webkit 的 bugzilla 里有一个放了很久的 issue,正是针对这个问题而言的,所以看来从[contenteditable]
里粘贴暂时看来是没戏了,有其他办法么?
其实俺一开始也到处搜索了一下,希望有人已经做了一个能实现粘贴图片功能的、支持不同浏览器的小 lib(或者 snippets),后来发现确实没有,所以俺就来做这个事儿吧,这样我的编辑器也可以简单地引用它来读取剪切板里的图片:
Ctrl + V
粘贴): http://puffant.github.io/paste.js/ 在这过程中我还找到了一些十分有用的资源 (上文中没有提到的):
FormData
。其实这个功能对于论坛很有用耶,看:
Ctrl + V