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 相关文章推荐
js 未结束的字符串常量错误解决方法
Jun 13 Javascript
javascript实现的使用方向键控制光标在table单元格中切换
Nov 17 Javascript
javascript 冒泡排序 正序和倒序实现代码
Dec 14 Javascript
IE网页js语法错误2行字符1、FF中正常的解决方法
Sep 09 Javascript
javascript与cookie 的问题详解
Nov 11 Javascript
JS和JQUERY获取页面大小,滚动条位置,元素位置(示例代码)
Dec 14 Javascript
EasyUi中的Combogrid 实现分页和动态搜索远程数据
Apr 01 Javascript
jQuery表格(Table)基本操作实例分析
Mar 10 Javascript
javascriptvoid(0)含义以及与&quot;#&quot;的区别讲解
Jan 19 Javascript
vue+element实现图片上传及裁剪功能
Jun 29 Javascript
vue项目两种方式实现竖向表格的思路分析
Apr 28 Vue.js
vue报错function () { [native code] },无法出现我们想要的内容 Unknown custom element
Apr 11 Vue.js
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调用mysql存储过程
2007/02/14 PHP
Bo-Blog专用的给Windows服务器的IIS Rewrite程序
2007/08/26 PHP
PHP array操作10个小技巧分享
2011/06/23 PHP
php读取flash文件高宽帧数背景颜色的方法
2015/01/06 PHP
ubutu 16.04环境下,PHP与mysql数据库,网页登录验证实例讲解
2017/07/20 PHP
javascript实现轮显新闻标题链接
2007/08/13 Javascript
在一个浏览器里呈现所有浏览器测试结果的前端测试工具的思路
2010/03/02 Javascript
基于jQuery的Tab选项框效果代码(插件)
2011/03/01 Javascript
javascript中style.left和offsetLeft的用法说明
2014/03/07 Javascript
在JavaScript中构建ArrayList示例代码
2014/09/17 Javascript
你一定会收藏的Nodejs代码片段
2016/02/04 NodeJs
JavaScript计时器用法分析【setTimeout和clearTimeout】
2017/01/18 Javascript
vue按需引入element Transfer 穿梭框
2017/09/30 Javascript
简化版的vue-router实现思路详解
2018/10/19 Javascript
微信小程序出现wx.getLocation再次授权问题的解决方法分析
2019/01/16 Javascript
vue全局自定义指令-元素拖拽的实现代码
2019/04/14 Javascript
Python3中的2to3转换工具使用示例
2015/06/12 Python
numpy自动生成数组详解
2017/12/15 Python
python2和python3在处理字符串上的区别详解
2019/05/29 Python
Django之使用celery和NGINX生成静态页面实现性能优化
2019/10/08 Python
如何提高python 中for循环的效率
2020/04/15 Python
python使用opencv resize图像不进行插值的操作
2020/07/05 Python
python 抓取知乎指定回答下视频的方法
2020/07/09 Python
python 生成正态分布数据,并绘图和解析
2020/12/21 Python
关于 HTML5 的七个传说小结
2012/04/12 HTML / CSS
发现世界上最好的珠宝设计师:JewelStreet
2017/12/17 全球购物
英国高街奥特莱斯:Highstreet Outlet
2019/11/21 全球购物
C面试题
2015/10/08 面试题
出纳岗位职责模板
2013/11/27 职场文书
毕业生的自我评价范文
2013/12/31 职场文书
2014年学校教学工作总结
2014/12/06 职场文书
Python中tkinter的用户登录管理的实现
2021/04/22 Python
CSS中Single Div 绘图技巧的实现
2021/06/18 HTML / CSS
记一次Mysql不走日期字段索引的原因小结
2021/10/24 MySQL
MySQL高级进阶sql语句总结大全
2022/03/16 MySQL
JS实现简单的九宫格抽奖
2022/06/28 Javascript