Vue解析剪切板图片并实现发送功能


Posted in Javascript onFebruary 04, 2020

前言

我们在使用QQ进行聊天时,从别的地方Ctrl+C一张图片,然后在聊天窗口Ctrl+V,QQ就会将你刚才复制的图片粘贴到即将发送的消息容器里,按下Enter键,这张图片将会发送出去。接下来跟各位开发者分享下这项功能在Vue中如何来实现。先跟大家展示下最终实现的效果。在线体验地址

Vue解析剪切板图片并实现发送功能

实现思路

  • 页面挂载时监听剪切板粘贴事件
  • 监听文件流
  • 读取文件流中的数据
  • 创建img标签
  • 将获取到的base64码赋值到img标签的src属性
  • 将生成的img标签append到即将发送的消息容器里
  • 监听回车事件
  • 获取可编辑div容器中的所有子元素
  • 遍历获取到的元素,找出img元素
  • 判断当前img元素是否有alt属性(表情插入时有alt属性),
  • 如果没有alt属性当前元素就是图片
  • 将base64格式的图片转成文件上传至服务器
  • 上传成功后,将服务器返回的图片地址推送到websocket服务
  • 客户端收到推送后,渲染页面

实现过程

本片文章主要讲解剪切板图片的解析以及将base64图片转换成文件上传至服务器,下方代码中的axios的封装以及websocket的配置与使用可参考我的另外两篇文章:Vue合理配置axios并在项目中进行实际应用和Vue合理配置WebSocket并实现群聊

监听剪切板事件(mounted生命周期中),将图片渲染到即将发送到消息容器里

const that = this;
document.body.addEventListener('paste', function (event) {
 // 自己写的一个全屏加载插件,文章地址:https://juejin.im/post/5e3307145188252c30002fa7
 that.$fullScreenLoading.show("读取图片中");
 // 获取当前输入框内的文字
 const oldText = that.$refs.msgInputContainer.textContent;
 // 读取图片
 let items = event.clipboardData && event.clipboardData.items;
 let file = null;
 if (items && items.length) {
 // 检索剪切板items
 for (let i = 0; i < items.length; i++) {
  if (items[i].type.indexOf('image') !== -1) {
  file = items[i].getAsFile();
  break;
  }
 }
 }
 // 预览图片
 const reader = new FileReader();
 reader.onload = function(event) {
 // 图片内容
 const imgContent = event.target.result;
 // 创建img标签
 let img = document.createElement('img');//创建一个img
 // 获取当前base64图片信息,计算当前图片宽高以及压缩比例
 let imgObj = new Image();
 let imgWidth = "";
 let imgHeight = "";
 let scale = 1;
 imgObj.src = imgContent;
 imgObj.onload = function() {
  // 计算img宽高
  if(this.width<400){
  imgWidth = this.width;
  imgHeight = this.height;
  }else{
  // 输入框图片显示缩小10倍
  imgWidth = this.width/10;
  imgHeight = this.height/10;
  // 图片宽度大于1920,图片压缩5倍
  if(this.width>1920){
   // 真实比例缩小5倍
   scale = 5;
  }
  }
  // 设置可编辑div中图片宽高
  img.width = imgWidth;
  img.height = imgHeight;
  // 压缩图片,渲染页面
  that.compressPic(imgContent,scale,function (newBlob,newBase) {
  // 删除可编辑div中的图片名称
  that.$refs.msgInputContainer.textContent = oldText;
  img.src = newBase; //设置链接
  // 图片渲染
  that.$refs.msgInputContainer.append(img);
  that.$fullScreenLoading.hide();
  });
 };
 };
 reader.readAsDataURL(file);
});

base64图片压缩函数

// 参数: base64地址,压缩比例,回调函数(返回压缩后图片的blob和base64)
 compressPic:function(base64, scale, callback){
 const that = this;
 let _img = new Image();
 _img.src = base64;
 _img.onload = function() {
  let _canvas = document.createElement("canvas");
  let w = this.width / scale;
  let h = this.height / scale;
  _canvas.setAttribute("width", w);
  _canvas.setAttribute("height", h);
  _canvas.getContext("2d").drawImage(this, 0, 0, w, h);
  let base64 = _canvas.toDataURL("image/jpeg");
  // 当canvas对象的原型中没有toBlob方法的时候,手动添加该方法
  if (!HTMLCanvasElement.prototype.toBlob) {
  Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
   value: function (callback, type, quality) {
   let binStr = atob(this.toDataURL(type, quality).split(',')[1]),
    len = binStr.length,
    arr = new Uint8Array(len);
   for (let i = 0; i < len; i++) {
    arr[i] = binStr.charCodeAt(i);
   }
   callback(new Blob([arr], {type: type || 'image/png'}));
   }
  });
  }else{
  _canvas.toBlob(function(blob) {
   if(blob.size > 1024*1024){
   that.compressPic(base64, scale, callback);
   }else{
   callback(blob, base64);
   }
  }, "image/jpeg");
  }
 }
 }

完善消息发送函数,获取输入框里的所有子元素,找出base64图片将其转为文件并上传至服务器(此处需要注意:base64转文件时,需要用正则表达式删掉base64图片的前缀),将当前图片地址推送至websocket服务。

对下述代码有不理解的地方,可阅读我的另一篇文章:Vue实现图片与文字混输,

sendMessage: function (event) {
 if (event.keyCode === 13) {
 // 阻止编辑框默认生成div事件
 event.preventDefault();
 let msgText = "";
 // 获取输入框下的所有子元素
 let allNodes = event.target.childNodes;
 for (let item of allNodes) {
  // 判断当前元素是否为img元素
  if (item.nodeName === "IMG") {
  if (item.alt === "") {
   // 是图片
   let base64Img = item.src;
   // 删除base64图片的前缀
   base64Img = base64Img.replace(/^data:image\/\w+;base64,/, "");
   //随机文件名
   let fileName = (new Date()).getTime() + ".jpeg";
   //将base64转换成file
   let imgFile = this.convertBase64UrlToImgFile(base64Img, fileName, 'image/jpeg');
   let formData = new FormData();
   // 此处的file与后台取值时的属性一样,append时需要添加文件名,否则一直时blob
   formData.append('file', imgFile, fileName);
   // 将图片上传至服务器
   this.$api.fileManageAPI.baseFileUpload(formData).then((res) => {
   const msgImgName = `/${res.fileName}/`;
   // 消息发送: 发送图片
   this.$socket.sendObj({
    msg: msgImgName,
    code: 0,
    username: this.$store.state.username,
    avatarSrc: this.$store.state.profilePicture,
    userID: this.$store.state.userID
   });
   // 清空输入框中的内容
   event.target.innerHTML = "";
   });
  } else {
   msgText += `/${item.alt}/`;
  }
  } else {
  // 获取text节点的值
  if (item.nodeValue !== null) {
   msgText += item.nodeValue;
  }
  }
 }
 // 消息发送: 发送文字,为空则不发送
 if (msgText.trim().length > 0) {
  this.$socket.sendObj({
  msg: msgText,
  code: 0,
  username: this.$store.state.username,
  avatarSrc: this.$store.state.profilePicture,
  userID: this.$store.state.userID
  });
  // 清空输入框中的内容
  event.target.innerHTML = "";
 }
 }
}

base64图片转flie

// base64转file
convertBase64UrlToImgFile: function (urlData, fileName, fileType) {
 // 转换为byte
 let bytes = window.atob(urlData);
 // 处理异常,将ascii码小于0的转换为大于0
 let ab = new ArrayBuffer(bytes.length);
 let ia = new Int8Array(ab);
 for (let i = 0; i < bytes.length; i++) {
 ia[i] = bytes.charCodeAt(i);
 }
 // 转换成文件,添加文件的type,name,lastModifiedDate属性
 let blob = new Blob([ab], {type: fileType});
 blob.lastModifiedDate = new Date();
 blob.name = fileName;
 return blob;
}

axios文件上传接口的封装(注意:需要设置"Content-Type":"multipart/form-data"})
/*
* 文件管理接口
* */
import services from '../plugins/axios'
import base from './base'; // 导入接口域名列表

const fileManageAPI = {
 // 单文件上传
 baseFileUpload(file){
 return services._axios.post(`${base.lkBaseURL}/uploads/singleFileUpload`,file,{headers:{"Content-Type":"multipart/form-data"}});
 }
};
export default fileManageAPI;

解析websocket推送的消息

// 消息解析
messageParsing: function (msgObj) {
 // 解析接口返回的数据并进行渲染
 let separateReg = /(\/[^/]+\/)/g;
 let msgText = msgObj.msgText;
 let finalMsgText = "";
 // 将符合条件的字符串放到数组里
 const resultArray = msgText.match(separateReg);
 if (resultArray !== null) {
  for (let item of resultArray) {
   // 删除字符串中的/符号
   item = item.replace(/\//g, "");
   // 判断是否为图片: 后缀为.jpeg
   if(this.isImg(item)){
    // 解析为img标签
    const imgTag = `<img src="${base.lkBaseURL}/upload/image/${item}" alt="聊天图片">`;
    // 替换匹配的字符串为img标签:全局替换
    msgText = msgText.replace(new RegExp(`/${item}/`, 'g'), imgTag);
   }
  }
  finalMsgText = msgText;
 } else {
  finalMsgText = msgText;
 }
 msgObj.msgText = finalMsgText;
 // 渲染页面
 this.senderMessageList.push(msgObj);
 // 修改滚动条位置
 this.$nextTick(function () {
  this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
 });
}

判断当前字符串是否为有图片后缀

// 判断是否为图片
isImg: function (str) {
 let objReg = new RegExp("[.]+(jpg|jpeg|swf|gif)$", "gi");
 return objReg.test(str);
}

踩坑记录

直接将base64格式的图片通过websocket发送至服务端

结果很明显,服务端websocket服务报错,报错原因:内容超过最大长度。

前端通过post请求将base64码传到服务端,服务端直接将base64码解析为图片保存至服务器

从下午2点折腾到晚上6点,一直在找Java解析base64图片存到服务器的方案,最终选择了放弃,采用了前端转换方式,这里的问题大概是前端传base64码到后端时,http请求会进行转义,导致后端解析得到的base64码是错误的,所以一直没有成功。

项目地址:chat-system

总结

以上所述是小编给大家介绍的Vue解析剪切板图片并实现发送功能,希望对大家有所帮助!

Javascript 相关文章推荐
ext combox 下拉框不出现自动提示,自动选中的解决方法
Feb 24 Javascript
jquery js 获取时间差、时间格式具体代码
Jun 05 Javascript
javascript的解析执行顺序在各个浏览器中的不同
Mar 17 Javascript
Jquery 获取指定标签的对象及属性的设置与移除
May 29 Javascript
js实现根据身份证号自动生成出生日期
Dec 15 Javascript
深入理解bootstrap框架之第二章整体架构
Oct 09 Javascript
node.js 和HTML5开发本地桌面应用程序
Dec 13 Javascript
Omi v1.0.2发布正式支持传递javascript表达式
Mar 21 Javascript
Node.js操作MongoDB数据库实例分析
Jan 19 Javascript
vue 路由懒加载中给 Webpack Chunks 命名的方法
Apr 24 Javascript
element中table高度自适应的实现
Oct 21 Javascript
使用jQuery实现购物车
Oct 29 jQuery
Vue实现剪切板图片压缩功能
Feb 04 #Javascript
Vue中keep-alive组件作用详解
Feb 04 #Javascript
WEB前端性能优化的7大手段详解
Feb 04 #Javascript
JavaScript对象属性操作实例解析
Feb 04 #Javascript
JavaScript this使用方法图解
Feb 04 #Javascript
解决微信小程序scroll-view组件无横向滚动的问题
Feb 04 #Javascript
JavaScript原型继承和原型链原理详解
Feb 04 #Javascript
You might like
用PHP制作静态网站的模板框架(四)
2006/10/09 PHP
php文件上传表单摘自drupal的代码
2011/02/15 PHP
PHP实现上传文件并存进数据库的方法
2015/07/16 PHP
利用php生成验证码
2017/02/23 PHP
php+croppic.js实现剪切上传图片功能
2018/08/14 PHP
关于Mozilla浏览器不支持innerText的解决办法
2011/01/01 Javascript
js判断一个元素是否为另一个元素的子元素的代码
2012/03/21 Javascript
jquery.cookie.js使用指南
2015/01/05 Javascript
JS从一组数据中找到指定的单条数据的方法
2016/06/02 Javascript
详解js的事件代理(委托)
2016/12/22 Javascript
微信小程序左右滑动切换页面详解及实例代码
2017/02/28 Javascript
Node.js中的http请求客户端示例(request client)
2017/05/04 Javascript
jquery tmpl模板(实例讲解)
2017/09/02 jQuery
JS实现合并json对象的方法
2017/10/10 Javascript
简化vuex的状态管理方案的方法
2018/06/02 Javascript
Bootstrap-table自定义可编辑每页显示记录数
2018/09/07 Javascript
vue实现下拉加载其实没那么复杂
2019/08/13 Javascript
解决vue 子组件修改父组件传来的props值报错问题
2019/11/09 Javascript
vue+element table表格实现动态列筛选的示例代码
2021/01/14 Vue.js
[01:21:07]EG vs Liquid 2018国际邀请赛淘汰赛BO3 第一场 8.25
2018/08/29 DOTA
Python的Django框架中的URL配置与松耦合
2015/07/15 Python
Python首次安装后运行报错(0xc000007b)的解决方法
2016/10/18 Python
Python利用递归和walk()遍历目录文件的方法示例
2017/07/14 Python
Python生命游戏实现原理及过程解析(附源代码)
2019/08/01 Python
Python代码注释规范代码实例解析
2020/08/14 Python
Python实现加密的RAR文件解压的方法(密码已知)
2020/09/11 Python
Python用Jira库来操作Jira
2020/12/28 Python
css3实现元素环绕中心点布局的方法示例
2019/01/15 HTML / CSS
HTML5之web workers_动力节点Java学院整理
2017/07/17 HTML / CSS
技术学校毕业生求职信分享
2013/12/02 职场文书
三方合作协议书范本
2014/04/18 职场文书
幼儿园国培研修日志
2015/11/13 职场文书
教你怎么用Python操作MySql数据库
2021/05/31 Python
浅谈sql_@SelectProvider及使用注意说明
2021/08/04 Java/Android
漫画「狩龙人拉格纳」公开TV动画预告图
2022/03/22 日漫
解决 Redis 秒杀超卖场景的高并发
2022/04/12 Redis