js获取剪切板内容,js控制图片粘贴。

jopen 4年前

在用户执行粘贴操作的时候, js 能够获得剪切板的内容,本文讨论一下这个问题。

目前只有 Chrome 支持获取剪切板中的图片数据。还好需要这个功能的产品目前只支持 Chrome 和 Safari ,一些 Chrome 的新特性是可以尽情使用了,还是能够覆盖到大部分用户的。所以本文只讨论 Chrome 如何使用和如何阻止 Safari ,原理大概了解了,再研究其他浏览器相关的问题就容易多了。

paste 事件

可以用 js 给页面中的元素绑定 paste 事件的方法,当用户鼠标在该元素上或者该元素处于 focus 状态,绑定到 paste 事件的方法就运行了。

绑定的元素不一定是 input ,普通的 div 也是可以绑定的,如果是给 document 绑定了,就相当于全局了,任何时候的粘贴操作都会触发。

事件对象

获取事件对象

先写一下事件绑定的代码

pasteEle.addEventListener("paste", function (e){      if ( !(e.clipboardData && e.clipboardData.items) ) {          return ;      }  });

粘贴事件提供了一个 clipboardData 的属性,如果该属性有 items 属性,那么就可以查看 items 中是否有图片类型的数据了。 Chrome 有该属性, Safari 没有。

clipboardData 介绍

介绍一下 clipboardData 对象,它实际上是一个 DataTransfer 类型的对象, DataTransfer 是拖动产生的一个对象,但实际上粘贴事件也是它。

clipboardData 的属性介绍

属性 类型 说明
dropEffect String 默认是 none
effectAllowed String 默认是 uninitialized
files FileList 粘贴操作为空List
items DataTransferItemList 剪切板中的各项数据
types Array 剪切板中的数据类型 该属性在Safari下比较混乱

items 介绍

items 是一个 DataTransferItemList 对象,自然里面都是 DataTransferItem 类型的数据了。

属性

items 的 DataTransferItem 有两个属性 kind 和 type

属性 说明
kind 一般为 string 或者 file
type 具体的数据类型,例如具体是哪种类型字符串或者哪种类型的文件,即 MIME-Type

方法

方法 参数 说明
getAsFile 如果 kind 是 file ,可以用该方法获取到文件
getAsString 回调函数 如果 kind 是 string ,可以用该方法获取到字符串,字符串需要用回调函数得到,回调函数的第一个参数就是剪切板中的字符串

在原型上还有一些其他方法,不过在处理剪切板操作的时候一般用不到了。

types 介绍

一般 types 中常见的值有 text/plain 、 text/html 、 Files 。

说明
text/plain 普通字符串
text/html 带有样式的html
Files 文件(例如剪切板中的数据)

简单demo

pasteEle.addEventListener("paste", function (e){      if ( !(e.clipboardData && e.clipboardData.items) ) {          return ;      }        for (var i = 0, len = e.clipboardData.items.length; i < len; i++) {          var item = e.clipboardData.items[i];            if (item.kind === "string") {              item.getAsString(function (str) {                  // str 是获取到的字符串              })          } else if (item.kind === "file") {              var pasteFile = item.getAsFile();              // pasteFile就是获取到的文件          }      }  });

注意如果是 string 类型的数据,可能针对具体是 text/plain 、 text/html 进行分别的处理。

问题来了

一切看似都很顺利,如果用户粘贴了图片,通过上面的方法我们是可以获取到,可以对图片进行上传等操作了。

首先要说一下js通过剪切板能获取到的图片是怎么来的,它必须是用QQ截图或者系统截图功能截下来的图片,或者是网页上某个图片单击右键复制图片等。

但是如果用户复制 Mac 的 Finder 中的一个图片文件,实际上js是没有办法获取到这个图片的。但是js确实会获得一个图片类型的文件,这个图片实际上图片在电脑中的图标标识,说的比较抽象,直接上图。

如果复制的是 JPEG 图片,粘贴过来的却是 Mac 上的文件缩略图,后面依次是 PNG 、 GIF 、 ZIP 、 DMG 、 Mac目录 的文件缩略图。

很明显,这不是我们期待得到的粘贴的结果,我们期待得到文件,但实际上却得到该文件在操作系统上的缩略图。

不过粘贴事件带来的数据还有一个字符串,就是该文件的名字,所以可以用下面的方法Hack掉。

    var cbd = e.clipboardData;      if(cbd.items && cbd.items.length === 2 && cbd.items[0].kind === "string" && cbd.items[1].kind === "file" &&              cbd.types && cbd.types.length === 2 && cbd.types[0] === "text/plain" && cbd.types[1] === "Files"){          return;      }

这么多的判断条件,基本可以确定通过剪切板过来的是粘贴的文件。我刚才测试了 Windows 的 Chrome ,不会有这个问题,当然也不能通过复制文件的方法得到任何文件。

问题又来了

当我打算写这篇博客的时候, Chrome 开发版已经升级到了49,上面的Bug突然消失了,囧。

所以上面的Hack应该加上版本限制了。

var ua = window.navigator.userAgent;  ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49

应该在上面的Hack再加上这两个判断,即是 Mac 下的 Chrome 49版本以下就要 return 。

探究过程走的一点弯路

由于公司IM系统正在迁移到V2消息系统,而且现有的文件类库没有办法满足业务需求,要自己封装一个文件上传库。

然后副总找到产品经理,说新版怎么不支持 Excel 的粘贴,临时排期一天修复这个问题,当时是这样解决的,如果 items 长度是1并且是文件类型(单纯粘贴一个文件),则上传,如果 items 长度是4且第4个是文件类型(经过测试是Excel的粘贴结果),则上传。

当时担心由于用户各种误操作,粘贴了不该粘贴的东西,文件上传错误,用了这种白名单机制去过滤,但是万一以后有比 Excel 粘贴得到的数据更其他的类型,就需要单独写代码兼容,所以,现在改成了如果判断是有Bug的情况,直接 return ,属于黑名单机制,这样以后再发现黑名单的情况,再添加。

可以拿来就用的代码

// demo 程序将粘贴事件绑定到 document 上  document.addEventListener("paste", function (e) {      var cbd = e.clipboardData;      var ua = window.navigator.userAgent;        // 如果是 Safari 直接 return      if ( !(e.clipboardData && e.clipboardData.items) ) {          return ;      }            // Mac平台下Chrome49版本以下 复制Finder中的文件的Bug Hack掉      if(cbd.items && cbd.items.length === 2 && cbd.items[0].kind === "string" && cbd.items[1].kind === "file" &&          cbd.types && cbd.types.length === 2 && cbd.types[0] === "text/plain" && cbd.types[1] === "Files" &&          ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49){          return;      }        for(var i = 0; i < cbd.items.length; i++) {          var item = cbd.items[i];          if(item.kind == "file"){              var blob = item.getAsFile();              if (blob.size === 0) {                  return;              }              // blob 就是从剪切板获得的文件 可以进行上传或其他操作          }      }  }, false);
</div>

来自: http://segmentfault.com/a/1190000004288686