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 相关文章推荐
JS+DIV实现鼠标划过切换层效果的实例代码
Nov 26 Javascript
AngularJs concepts详解及示例代码
Sep 01 Javascript
老生常谈javascript变量的命名规范和注释
Sep 29 Javascript
JS实现按钮控制计时开始和停止功能
Jul 27 Javascript
深入掌握 react的 setState的工作机制
Sep 27 Javascript
ES6 javascript的异步操作实例详解
Oct 30 Javascript
JS严格模式知识点总结
Feb 27 Javascript
JS计算斐波拉切代码实例
Sep 12 Javascript
小程序怎样让wx.navigateBack更好用的方法实现
Nov 01 Javascript
微信小程序点击保存图片到本机功能
Dec 13 Javascript
微信小程序转化为uni-app项目的方法示例
May 22 Javascript
原生js实现简单轮播图
Oct 26 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边学边教》(04.编写简易的通讯录――视频教程1)
2006/12/13 PHP
php 修改zen-cart下单和付款流程以防止漏单
2010/03/08 PHP
php calender(日历)二个版本代码示例(解决2038问题)
2013/12/24 PHP
Smarty保留变量用法分析
2016/05/23 PHP
jquery+json实现的搜索加分页效果
2010/03/31 Javascript
js中substring和substr的详细介绍与用法
2013/08/29 Javascript
html5+javascript制作简易画板附图
2014/04/25 Javascript
Javascript单元测试框架QUnitjs详细介绍
2014/05/08 Javascript
Javascript封装DOMContentLoaded事件实例
2014/06/12 Javascript
Javascript实现汉字和拼音互转的终极方案
2016/10/19 Javascript
vue之数据交互实例代码
2017/06/20 Javascript
记一次webpack3升级webpack4的踩坑经历
2018/06/12 Javascript
Vue全局分页组件的实现代码
2018/08/10 Javascript
vuex的module模块用法示例
2018/11/12 Javascript
layui文件上传控件带更改后数据传值的方法
2019/09/23 Javascript
微信小程序加载机制及运行机制图解
2019/11/27 Javascript
uniapp微信小程序实现一个页面多个倒计时
2020/11/01 Javascript
Vue过滤器,生命周期函数和vue-resource简单介绍
2021/01/12 Vue.js
[05:45]Ti4观战指南(下)
2014/07/07 DOTA
Python在Windows和在Linux下调用动态链接库的教程
2015/08/18 Python
Python彩色化Linux的命令行终端界面的代码实例分享
2016/07/02 Python
对python实现模板生成脚本的方法详解
2019/01/30 Python
python3实现zabbix告警推送钉钉的示例
2019/02/20 Python
python requests更换代理适用于IP频率限制的方法
2019/08/21 Python
python3通过udp实现组播数据的发送和接收操作
2020/05/05 Python
Pytorch学习之torch用法----比较操作(Comparison Ops)
2020/06/28 Python
解决Pymongo insert时会自动添加_id的问题
2020/12/05 Python
html5中的一些标签学习(心得)
2016/10/18 HTML / CSS
馥绿德雅美国官方网站:Rene Furterer头皮护理专家
2019/05/01 全球购物
财务管理专业自荐信范文
2013/12/24 职场文书
好习惯伴我成长演讲稿
2014/05/21 职场文书
电子商务优秀毕业生求职信
2014/07/11 职场文书
大学新生军训自我鉴定
2014/09/18 职场文书
三十年同学聚会感言
2015/07/30 职场文书
志愿服务心得体会
2016/01/15 职场文书
Linux安装Docker详细教程
2022/07/07 Servers