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 相关文章推荐
JavaScript 学习笔记(四)
Dec 31 Javascript
基于jquery的表头固定的若干方法
Jan 27 Javascript
用jQuery中的ajax分页实现代码
Sep 20 Javascript
通过Javascript创建一个选择文件的对话框代码
Jun 16 Javascript
JS实现网页上随机产生超链接地址的方法
Nov 09 Javascript
JavaScript结合Bootstrap仿微信后台多图文界面管理
Jul 22 Javascript
JavaScript cookie详解及简单实例应用
Dec 31 Javascript
原生JavaScript实现精美的淘宝轮播图效果示例【附demo源码下载】
May 27 Javascript
基于Vue、Vuex、Vue-router实现的购物商城(原生切换动画)效果
Jan 09 Javascript
微信小程序中进行地图导航功能的实现方法
Jun 29 Javascript
一文快速了解JQuery中的AJAX
May 31 jQuery
从零搭一个自用的前端脚手架的方法步骤
Sep 23 Javascript
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中的MVC模式运用技巧
2007/05/03 PHP
在JavaScript中调用php程序
2009/03/09 PHP
php正则表达匹配中文问题分析小结
2012/03/25 PHP
基于php缓存的详解
2013/05/15 PHP
php实现文件下载代码分享
2014/08/19 PHP
PHP获取数组中单列值的方法
2017/06/10 PHP
php 删除指定文件夹的实例讲解
2017/07/25 PHP
浅谈PHP中如何实现Hook机制
2017/11/14 PHP
纯文字版返回顶端的js代码
2013/08/01 Javascript
jquery easyui使用心得
2014/07/07 Javascript
浅析webapp框架AngularUI的demo
2014/12/21 Javascript
jQuery实现按钮的点击 全选/反选 单选框/复选框 文本框 表单验证
2015/06/25 Javascript
javascript 使用for循环时该注意的问题-附问题总结
2015/08/19 Javascript
jQuery遍历DOM的父级元素、子级元素和同级元素的方法总结
2016/07/07 Javascript
JavaScript实现反转字符串的方法详解
2017/04/27 Javascript
Easyui 关闭jquery-easui tab标签页前触发事件的解决方法
2019/04/28 jQuery
vue.js实现备忘录demo
2019/06/26 Javascript
JavaScript之Blob对象类型的具体使用方法
2019/11/29 Javascript
vue-路由精讲 二级路由和三级路由的作用
2020/08/06 Javascript
《Python之禅》中对于Python编程过程中的一些建议
2015/04/03 Python
Python heapq使用详解及实例代码
2017/01/25 Python
使用Python代码实现Linux中的ls遍历目录命令的实例代码
2019/09/07 Python
xadmin使用formfield_for_dbfield函数过滤下拉表单实例
2020/04/07 Python
Python Excel vlookup函数实现过程解析
2020/06/22 Python
如何将anaconda安装配置的mmdetection环境离线拷贝到另一台电脑
2020/10/15 Python
美国首屈一指的礼品篮供应商:GiftTree
2018/01/06 全球购物
Dodax奥地利:音乐、电影、书籍、玩具、电子产品等
2019/08/31 全球购物
高中军训感言200字
2014/02/23 职场文书
节能宣传周活动总结
2014/05/08 职场文书
小学师德师风演讲稿
2014/09/02 职场文书
公司授权委托书格式样本
2014/10/01 职场文书
2014年企业党支部工作总结
2014/12/04 职场文书
经验交流材料格式
2014/12/30 职场文书
刑事辩护词范文
2015/05/21 职场文书
五年级语文教学反思
2016/03/03 职场文书
Python中json.load()和json.loads()有哪些区别
2021/06/07 Python