1. 程式人生 > 程式設計 >Vue解析剪下板圖片並實現傳送功能

Vue解析剪下板圖片並實現傳送功能

前言

我們在使用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,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,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,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,userID: this.$store.state.userID
  });
  // 清空輸入框中的內容
  event.target.innerHTML = "";
 }
 }
}

base64圖片轉flie

// base64轉file
convertBase64UrlToImgFile: function (urlData,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解析剪下板圖片並實現傳送功能,希望對大家有所幫助!