js实现ctrl+v粘贴上传图片(兼容chrome、firefox、ie11)


Posted in Javascript onMarch 09, 2016

我们或多或少都使用过各式各样的富文本编辑器,其中有一个很方便功能,复制一张图片然后粘贴进文本框,这张图片就被上传了,那么这个方便的功能是如何实现的呢?

原理分析
提取操作:复制=>粘贴=>上传
在这个操作过程中,我们需要做的就是:监听粘贴事件=>获取剪贴板里的内容=>发请求上传
为方便理解下文,需要先明白几点:

  • 我们只能上传网页图(在网页上右键图片,然后复制)和截图(截图工具截的图片,eg:qq截图),不能粘贴上传系统里的图片(从桌面上、硬盘里复制),他们是存在完全不同的地方的。
  • 截图工具截的图与在网页点击右键复制的图是有些不同的,因此处理方式也不一样。
  • 知悉paste event这个事件:当进行粘贴(右键paste/ctrl+v)操作时,该动作将触发名为'paste'的剪贴板事件,这个事件的触发是在剪贴板里的数据插入到目标元素之前。如果目标元素(光标所在位置)是可编辑的元素(eg:设置了contenteditable属性的div。textarea并不行。),粘贴动作将把剪贴板里的数据,以最合适的格式,插入到目标元素里;如果目标元素不可编辑,则不会插入数据,但依然触发paste event。数据在粘贴的过程中是只读的。此段是翻译于w3.org_the-paste-action。
  • 可惜的是,经过试验,发现chrome(当前最新版)、firefox(当前最新版)、ie11对paste事件的实现并不是完全按照w3c来的,各自也有区别(w3c的paste标准也因此只是草案阶段)。

test代码及截图如下:

chrome:

<textarea ></textarea> 
<div contenteditable style="width: 100px;height: 100px; border:1px solid"> 
</div> 
<script> 
document.addEventListener('paste', function (event) { 
  console.log(event) 
})
</script>

js实现ctrl+v粘贴上传图片(兼容chrome、firefox、ie11)

js实现ctrl+v粘贴上传图片(兼容chrome、firefox、ie11)

js实现ctrl+v粘贴上传图片(兼容chrome、firefox、ie11)

js实现ctrl+v粘贴上传图片(兼容chrome、firefox、ie11)

js实现ctrl+v粘贴上传图片(兼容chrome、firefox、ie11)

js实现ctrl+v粘贴上传图片(兼容chrome、firefox、ie11)

  1. event有clipboardData属性,且clipboardData有item属性,clipboardData.item中的元素(对象)有type和kind属性;
  2. 无论在哪进行粘贴,均可触发paste事件;
  3. 在div(未特殊声明时,本文div均指设置了contenteditable属性的div) 里粘贴截图,不显示图片。img.src为base64编码字符串;
  4. 在div里粘贴网页图片,直接显示图片,img.src为图片地址。

firefox:

js实现ctrl+v粘贴上传图片(兼容chrome、firefox、ie11)

js实现ctrl+v粘贴上传图片(兼容chrome、firefox、ie11)

  1. event有clipboardData属性,clipboardData没有item属性;
  2. 只有在textarea里或者可编辑的div(里才粘贴才触发paste事件;
  3. 在div里粘贴截图,直接显示图片,img.src为base64编码字符串;
  4. 在div里粘贴网页图片,表现同chrome。

ie11:(不截图了,可自行试验,其他浏览器同理<( ̄? ̄)/,因为懒...)

  • event没有clipboardData属性;
  • 只在可编辑的div中粘贴才触发paste事件;
  • 在div里粘贴截图,直接显示图片,img.src为base64编码字符串;
  • 在div里粘贴网页图片,表现同chrome。

监听了paste事件,也知道了表现形式,接下来就是如何获取数据了:
chrome有特定的方法,利用clipboardData.items、getAsFile()、new FileReader()等api可以在paste回调函数里获取到剪贴板里图片的base64编码字符串(无论是截图粘贴的还是网页图片复制粘贴的),ie11,firefox没有这样的api,不过依然有办法可以获取,因为数据已经表现在img的src里了,对于截图粘贴的,直接取img的src属性值(base64),对于网页粘贴的,则把地址传给后台,然后根据地址down下来,存在自己的服务器,最后把新地址返回来交给前端展示就ok了。为了保持一致性便于管理,统一将所有情况(截图、网页)中的img的src属性替换为自己存储的地址。因此可以得到以下核心代码(注释很全哦~~):

html展示:

<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
body {
  display: -webkit-flex; 
  display: flex;    
  -webkit-justify-content: center;
  justify-content: center;
}
#tar_box {
  width: 500px;
  height: 500px;
  border: 1px solid red;
}
</style>

前端js处理逻辑:

document.addEventListener('paste', function (event) {
  console.log(event)
  var isChrome = false;
  if ( event.clipboardData || event.originalEvent ) {
    //not for ie11  某些chrome版本使用的是event.originalEvent
    var clipboardData = (event.clipboardData || event.originalEvent.clipboardData);
    if ( clipboardData.items ) {
      // for chrome
      var  items = clipboardData.items,
        len = items.length,
        blob = null;
      isChrome = true;
      //items.length比较有意思,初步判断是根据mime类型来的,即有几种mime类型,长度就是几(待验证)
      //如果粘贴纯文本,那么len=1,如果粘贴网页图片,len=2, items[0].type = 'text/plain', items[1].type = 'image/*'
      //如果使用截图工具粘贴图片,len=1, items[0].type = 'image/png'
      //如果粘贴纯文本+HTML,len=2, items[0].type = 'text/plain', items[1].type = 'text/html'
      // console.log('len:' + len);
      // console.log(items[0]);
      // console.log(items[1]);
      // console.log( 'items[0] kind:', items[0].kind );
      // console.log( 'items[0] MIME type:', items[0].type );
      // console.log( 'items[1] kind:', items[1].kind );
      // console.log( 'items[1] MIME type:', items[1].type );

      //阻止默认行为即不让剪贴板内容在div中显示出来
      event.preventDefault();

      //在items里找粘贴的image,据上面分析,需要循环  
      for (var i = 0; i < len; i++) {
        if (items[i].type.indexOf("image") !== -1) {
          // console.log(items[i]);
          // console.log( typeof (items[i]));

          //getAsFile() 此方法只是living standard firefox ie11 并不支持        
          blob = items[i].getAsFile();
        }
      }
      if ( blob !== null ) {
        var reader = new FileReader();
        reader.onload = function (event) {
          // event.target.result 即为图片的Base64编码字符串
          var base64_str = event.target.result
          //可以在这里写上传逻辑 直接将base64编码的字符串上传(可以尝试传入blob对象,看看后台程序能否解析)
          uploadImgFromPaste(base64_str, 'paste', isChrome);
        }
        reader.readAsDataURL(blob); 
      }
    } else {
      //for firefox
      setTimeout(function () {
        //设置setTimeout的原因是为了保证图片先插入到div里,然后去获取值
        var imgList = document.querySelectorAll('#tar_box img'),
          len = imgList.length,
          src_str = '',
          i;
        for ( i = 0; i < len; i ++ ) {
          if ( imgList[i].className !== 'my_img' ) {
            //如果是截图那么src_str就是base64 如果是复制的其他网页图片那么src_str就是此图片在别人服务器的地址
            src_str = imgList[i].src;
          }
        }
        uploadImgFromPaste(src_str, 'paste', isChrome);
      }, 1);
    }
  } else {
    //for ie11
    setTimeout(function () {
      var imgList = document.querySelectorAll('#tar_box img'),
        len = imgList.length,
        src_str = '',
        i;
      for ( i = 0; i < len; i ++ ) {
        if ( imgList[i].className !== 'my_img' ) {
          src_str = imgList[i].src;
        }
      }
      uploadImgFromPaste(src_str, 'paste', isChrome);
    }, 1);
  }
})

function uploadImgFromPaste (file, type, isChrome) {
  var formData = new FormData();
  formData.append('image', file);
  formData.append('submission-type', type);

  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/upload_image_by_paste');
  xhr.onload = function () {
    if ( xhr.readyState === 4 ) {
      if ( xhr.status === 200 ) {
        var data = JSON.parse( xhr.responseText ),
          tarBox = document.getElementById('tar_box');
        if ( isChrome ) {
          var img = document.createElement('img');
          img.className = 'my_img';
          img.src = data.store_path;
          tarBox.appendChild(img);
        } else {
          var imgList = document.querySelectorAll('#tar_box img'),
            len = imgList.length,
            i;
          for ( i = 0; i < len; i ++) {
            if ( imgList[i].className !== 'my_img' ) {
              imgList[i].className = 'my_img';
              imgList[i].src = data.store_path;
            }
          }
        }

      } else {
        console.log( xhr.statusText );
      }
    };
  };
  xhr.onerror = function (e) {
    console.log( xhr.statusText );
  }
  xhr.send(formData);
}

用express.js搭的简易后台的接收逻辑:

router.post('/', upload.array(), function (req, res, next) {
//1.获取客户端传来的src_str字符串=>判断是base64还是普通地址=>获取图片类型后缀(jpg/png etc)
//=>如果是base64替换掉"前缀"("data:image\/png;base64," etc)
//2.base64 转为 buffer对象 普通地址则先down下来
//3.写入硬盘(后续可以将地址存入数据库)
//4.返回picture地址
var src_str = req.body.image,
  timestamp = new Date().getTime();
if ( src_str.match(/^data:image\/png;base64,|^data:image\/jpg;base64,|^data:image\/jpg;base64,|^data:image\/bmp;base64,/) ) {
  //处理截图 src_str为base64字符串
  var pic_suffix = src_str.split(';',1)[0].split('/',2)[1],
    base64 = src_str.replace(/^data:image\/png;base64,|^data:image\/jpg;base64,|^data:image\/jpg;base64,|^data:image\/bmp;base64,/, ''),
    buf = new Buffer(base64, 'base64'),
    store_path = 'public/images/test_' + timestamp + '.' + pic_suffix;

  fs.writeFile(store_path, buf, function (err) {
    if (err) {
      throw err;
    } else {
      res.json({'store_path': store_path});
    }
  });
} else {// 处理非chrome的网页图片 src_str为图片地址
  var temp_array = src_str.split('.'),
    pic_suffix = temp_array[temp_array.length - 1],
    store_path = 'public/images/test_' + timestamp + '.' + pic_suffix,
    wstream = fs.createWriteStream(store_path);

  request(src_str).pipe(wstream);
  wstream.on('finish', function (err) {
    if( err ) {
      throw err;
    } else {
      res.json({"store_path": store_path});
    }
  });
}
});

需要node环境:安装node=>npm intall=>node app.js)

以上就是本文的全部内容,希望对大家的学习有所帮助。

Javascript 相关文章推荐
jQuery 选择器、DOM操作、事件、动画
Nov 25 Javascript
jQuery基础知识小结
Dec 22 Javascript
JavaScript使用Replace进行字符串替换的方法
Apr 14 Javascript
ECMAScript中函数function类型
Jun 03 Javascript
javascript表格的渲染组件
Jul 03 Javascript
Bootstrap源码学习笔记之bootstrap进度条
Dec 24 Javascript
Vue.2.0.5过渡效果使用技巧
Mar 16 Javascript
vue 如何添加全局函数或全局变量以及单页面的title设置总结
Jun 01 Javascript
基于pako.js实现gzip的压缩和解压功能示例
Jun 13 Javascript
聊聊Vue.js的template编译的问题
Oct 09 Javascript
JavaScript数据结构之单链表和循环链表
Nov 28 Javascript
微信小程序项目实践之主页tab选项实现
Jul 18 Javascript
基于jQuery的网页影音播放器jPlayer的基本使用教程
Mar 08 #Javascript
利用jQuery设计一个简单的web音乐播放器的实例分享
Mar 08 #Javascript
Bootstrap~多级导航(级联导航)的实现效果【附代码】
Mar 08 #Javascript
js实现数组冒泡排序、快速排序原理
Mar 08 #Javascript
Bootstrap多级导航栏(级联导航)的实现代码
Mar 08 #Javascript
javascript html实现网页版日历代码
Mar 08 #Javascript
一道关于JavaScript变量作用域的面试题
Mar 08 #Javascript
You might like
php求今天、昨天、明天时间戳的简单实现方法
2016/07/28 PHP
Laravel 解决419错误 -ajax请求错误的问题(CSRF验证)
2019/10/25 PHP
PHP sdk文档处理常用代码示例解析
2020/12/09 PHP
Jquery 基础学习笔记之文档处理
2009/05/29 Javascript
基于jquery的tab切换 js原理
2010/04/01 Javascript
网页中表单按回车就自动提交的问题的解决方案
2014/11/03 Javascript
浅谈javascript的Touch事件
2015/09/27 Javascript
jquery判断密码强度的验证代码
2020/04/22 Javascript
javascript判断回文数详解及实现代码
2017/02/03 Javascript
Vue单文件组件的如何使用方式介绍
2017/07/28 Javascript
Vue组件选项props实例详解
2017/08/18 Javascript
Vue官网todoMVC示例代码
2018/01/29 Javascript
利用jquery和BootStrap实现动态滚动条效果
2018/12/03 jQuery
js 实现ajax发送步骤过程详解
2019/07/25 Javascript
微信小程序进入广告实现代码实例
2019/09/19 Javascript
layui树形菜单动态遍历的例子
2019/09/23 Javascript
在vue中配置不同的代理同时访问不同的后台操作
2020/09/11 Javascript
JavaScript用document.write()输出换行的示例代码
2020/11/26 Javascript
Python日期操作学习笔记
2008/10/07 Python
Python 实现域名解析为ip的方法
2019/02/14 Python
python实现nao机器人手臂动作控制
2019/04/29 Python
Python从列表推导到zip()函数的5种技巧总结
2019/10/23 Python
Python matplotlib修改默认字体的操作
2020/03/05 Python
python实现简单坦克大战
2020/03/27 Python
使用python编写一个语音朗读闹钟功能的示例代码
2020/07/14 Python
Python urllib库如何添加headers过程解析
2020/10/05 Python
HTML5 标准将把互联网视频扔回到黑暗时代
2010/02/10 HTML / CSS
Nike爱尔兰官方网站:Nike.com (IE)
2018/03/12 全球购物
个人求职信范文
2014/05/24 职场文书
关于爱国的标语
2014/06/24 职场文书
2015年社区民政工作总结
2015/04/21 职场文书
公务员保密工作承诺书
2015/05/04 职场文书
2015年幼儿园保育工作总结
2015/05/12 职场文书
小程序后台PHP版本部署运行 LNMP+WNMP
2021/04/01 Servers
Pytorch GPU内存占用很高,但是利用率很低如何解决
2021/06/01 Python
spring 项目实现限流方法示例
2022/07/15 Java/Android