Vue解析剪切板圖片并實(shí)現(xiàn)發(fā)送功能
前言
我們?cè)谑褂肣Q進(jìn)行聊天時(shí),從別的地方Ctrl+C一張圖片,然后在聊天窗口Ctrl+V,QQ就會(huì)將你剛才復(fù)制的圖片粘貼到即將發(fā)送的消息容器里,按下Enter鍵,這張圖片將會(huì)發(fā)送出去。接下來(lái)跟各位開(kāi)發(fā)者分享下這項(xiàng)功能在Vue中如何來(lái)實(shí)現(xiàn)。先跟大家展示下最終實(shí)現(xiàn)的效果。在線體驗(yàn)地址

實(shí)現(xiàn)思路
- 頁(yè)面掛載時(shí)監(jiān)聽(tīng)剪切板粘貼事件
- 監(jiān)聽(tīng)文件流
- 讀取文件流中的數(shù)據(jù)
- 創(chuàng)建img標(biāo)簽
- 將獲取到的base64碼賦值到img標(biāo)簽的src屬性
- 將生成的img標(biāo)簽append到即將發(fā)送的消息容器里
- 監(jiān)聽(tīng)回車(chē)事件
- 獲取可編輯div容器中的所有子元素
- 遍歷獲取到的元素,找出img元素
- 判斷當(dāng)前img元素是否有alt屬性(表情插入時(shí)有alt屬性),
- 如果沒(méi)有alt屬性當(dāng)前元素就是圖片
- 將base64格式的圖片轉(zhuǎn)成文件上傳至服務(wù)器
- 上傳成功后,將服務(wù)器返回的圖片地址推送到websocket服務(wù)
- 客戶(hù)端收到推送后,渲染頁(yè)面
實(shí)現(xiàn)過(guò)程
本片文章主要講解剪切板圖片的解析以及將base64圖片轉(zhuǎn)換成文件上傳至服務(wù)器,下方代碼中的axios的封裝以及websocket的配置與使用可參考我的另外兩篇文章:Vue合理配置axios并在項(xiàng)目中進(jìn)行實(shí)際應(yīng)用和Vue合理配置WebSocket并實(shí)現(xiàn)群聊
監(jiān)聽(tīng)剪切板事件(mounted生命周期中),將圖片渲染到即將發(fā)送到消息容器里
const that = this;
document.body.addEventListener('paste', function (event) {
// 自己寫(xiě)的一個(gè)全屏加載插件,文章地址:https://juejin.im/post/5e3307145188252c30002fa7
that.$fullScreenLoading.show("讀取圖片中");
// 獲取當(dāng)前輸入框內(nèi)的文字
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;
}
}
}
// 預(yù)覽圖片
const reader = new FileReader();
reader.onload = function(event) {
// 圖片內(nèi)容
const imgContent = event.target.result;
// 創(chuàng)建img標(biāo)簽
let img = document.createElement('img');//創(chuàng)建一個(gè)img
// 獲取當(dāng)前base64圖片信息,計(jì)算當(dāng)前圖片寬高以及壓縮比例
let imgObj = new Image();
let imgWidth = "";
let imgHeight = "";
let scale = 1;
imgObj.src = imgContent;
imgObj.onload = function() {
// 計(jì)算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){
// 真實(shí)比例縮小5倍
scale = 5;
}
}
// 設(shè)置可編輯div中圖片寬高
img.width = imgWidth;
img.height = imgHeight;
// 壓縮圖片,渲染頁(yè)面
that.compressPic(imgContent,scale,function (newBlob,newBase) {
// 刪除可編輯div中的圖片名稱(chēng)
that.$refs.msgInputContainer.textContent = oldText;
img.src = newBase; //設(shè)置鏈接
// 圖片渲染
that.$refs.msgInputContainer.append(img);
that.$fullScreenLoading.hide();
});
};
};
reader.readAsDataURL(file);
});
base64圖片壓縮函數(shù)
// 參數(shù): base64地址,壓縮比例,回調(diào)函數(shù)(返回壓縮后圖片的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");
// 當(dāng)canvas對(duì)象的原型中沒(méi)有toBlob方法的時(shí)候,手動(dòng)添加該方法
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");
}
}
}
完善消息發(fā)送函數(shù),獲取輸入框里的所有子元素,找出base64圖片將其轉(zhuǎn)為文件并上傳至服務(wù)器(此處需要注意:base64轉(zhuǎn)文件時(shí),需要用正則表達(dá)式刪掉base64圖片的前綴),將當(dāng)前圖片地址推送至websocket服務(wù)。
對(duì)下述代碼有不理解的地方,可閱讀我的另一篇文章:Vue實(shí)現(xiàn)圖片與文字混輸,
sendMessage: function (event) {
if (event.keyCode === 13) {
// 阻止編輯框默認(rèn)生成div事件
event.preventDefault();
let msgText = "";
// 獲取輸入框下的所有子元素
let allNodes = event.target.childNodes;
for (let item of allNodes) {
// 判斷當(dāng)前元素是否為img元素
if (item.nodeName === "IMG") {
if (item.alt === "") {
// 是圖片
let base64Img = item.src;
// 刪除base64圖片的前綴
base64Img = base64Img.replace(/^data:image\/\w+;base64,/, "");
//隨機(jī)文件名
let fileName = (new Date()).getTime() + ".jpeg";
//將base64轉(zhuǎn)換成file
let imgFile = this.convertBase64UrlToImgFile(base64Img, fileName, 'image/jpeg');
let formData = new FormData();
// 此處的file與后臺(tái)取值時(shí)的屬性一樣,append時(shí)需要添加文件名,否則一直時(shí)blob
formData.append('file', imgFile, fileName);
// 將圖片上傳至服務(wù)器
this.$api.fileManageAPI.baseFileUpload(formData).then((res) => {
const msgImgName = `/${res.fileName}/`;
// 消息發(fā)送: 發(fā)送圖片
this.$socket.sendObj({
msg: msgImgName,
code: 0,
username: this.$store.state.username,
avatarSrc: this.$store.state.profilePicture,
userID: this.$store.state.userID
});
// 清空輸入框中的內(nèi)容
event.target.innerHTML = "";
});
} else {
msgText += `/${item.alt}/`;
}
} else {
// 獲取text節(jié)點(diǎn)的值
if (item.nodeValue !== null) {
msgText += item.nodeValue;
}
}
}
// 消息發(fā)送: 發(fā)送文字,為空則不發(fā)送
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
});
// 清空輸入框中的內(nèi)容
event.target.innerHTML = "";
}
}
}
base64圖片轉(zhuǎn)flie
// base64轉(zhuǎn)file
convertBase64UrlToImgFile: function (urlData, fileName, fileType) {
// 轉(zhuǎn)換為byte
let bytes = window.atob(urlData);
// 處理異常,將ascii碼小于0的轉(zhuǎn)換為大于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);
}
// 轉(zhuǎn)換成文件,添加文件的type,name,lastModifiedDate屬性
let blob = new Blob([ab], {type: fileType});
blob.lastModifiedDate = new Date();
blob.name = fileName;
return blob;
}
axios文件上傳接口的封裝(注意:需要設(shè)置"Content-Type":"multipart/form-data"})
/*
* 文件管理接口
* */
import services from '../plugins/axios'
import base from './base'; // 導(dǎo)入接口域名列表
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) {
// 解析接口返回的數(shù)據(jù)并進(jìn)行渲染
let separateReg = /(\/[^/]+\/)/g;
let msgText = msgObj.msgText;
let finalMsgText = "";
// 將符合條件的字符串放到數(shù)組里
const resultArray = msgText.match(separateReg);
if (resultArray !== null) {
for (let item of resultArray) {
// 刪除字符串中的/符號(hào)
item = item.replace(/\//g, "");
// 判斷是否為圖片: 后綴為.jpeg
if(this.isImg(item)){
// 解析為img標(biāo)簽
const imgTag = `<img src="${base.lkBaseURL}/upload/image/${item}" alt="聊天圖片">`;
// 替換匹配的字符串為img標(biāo)簽:全局替換
msgText = msgText.replace(new RegExp(`/${item}/`, 'g'), imgTag);
}
}
finalMsgText = msgText;
} else {
finalMsgText = msgText;
}
msgObj.msgText = finalMsgText;
// 渲染頁(yè)面
this.senderMessageList.push(msgObj);
// 修改滾動(dòng)條位置
this.$nextTick(function () {
this.$refs.messagesContainer.scrollTop = this.$refs.messagesContainer.scrollHeight;
});
}
判斷當(dāng)前字符串是否為有圖片后綴
// 判斷是否為圖片
isImg: function (str) {
let objReg = new RegExp("[.]+(jpg|jpeg|swf|gif)$", "gi");
return objReg.test(str);
}
踩坑記錄
直接將base64格式的圖片通過(guò)websocket發(fā)送至服務(wù)端
結(jié)果很明顯,服務(wù)端websocket服務(wù)報(bào)錯(cuò),報(bào)錯(cuò)原因:內(nèi)容超過(guò)最大長(zhǎng)度。
前端通過(guò)post請(qǐng)求將base64碼傳到服務(wù)端,服務(wù)端直接將base64碼解析為圖片保存至服務(wù)器
從下午2點(diǎn)折騰到晚上6點(diǎn),一直在找Java解析base64圖片存到服務(wù)器的方案,最終選擇了放棄,采用了前端轉(zhuǎn)換方式,這里的問(wèn)題大概是前端傳base64碼到后端時(shí),http請(qǐng)求會(huì)進(jìn)行轉(zhuǎn)義,導(dǎo)致后端解析得到的base64碼是錯(cuò)誤的,所以一直沒(méi)有成功。
項(xiàng)目地址:chat-system
總結(jié)
以上所述是小編給大家介紹的Vue解析剪切板圖片并實(shí)現(xiàn)發(fā)送功能,希望對(duì)大家有所幫助!
相關(guān)文章
vue項(xiàng)目環(huán)境搭建?啟動(dòng)?移植操作示例及目錄結(jié)構(gòu)分析
這篇文章主要介紹了vue項(xiàng)目環(huán)境搭建、啟動(dòng)、項(xiàng)目移植、項(xiàng)目目錄結(jié)構(gòu)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
Electron主進(jìn)程(Main?Process)與渲染進(jìn)程(Renderer?Process)通信詳解
這篇文章主要介紹了Electron主進(jìn)程(Main?Process)與渲染進(jìn)程(Renderer?Process)通信,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
Vue?2?和?Vue?3?中?toRefs函數(shù)的不用用法
Vue?是一款流行的JavaScript?框架,用于構(gòu)建用戶(hù)界面,在Vue2和?Vue3中,都存在一個(gè)名為toRefs的函數(shù),但其行為在這兩個(gè)版本中有所不同,這篇文章主要介紹了Vue2和Vue3中toRefs的區(qū)別,需要的朋友可以參考下2023-08-08
淺談Vue頁(yè)面級(jí)緩存解決方案feb-alive (下)
這篇文章主要介紹了淺談Vue頁(yè)面級(jí)緩存解決方案feb-alive(下),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
vue 計(jì)算屬性和偵聽(tīng)器的使用小結(jié)
這篇文章主要介紹了vue 計(jì)算屬性和偵聽(tīng)器的使用小結(jié),幫助大家更好的理解和使用vue框架,感興趣的朋友可以了解下2021-01-01
vue中遇到的坑之變化檢測(cè)問(wèn)題(數(shù)組相關(guān))
這篇文章主要介紹了vue中遇到的坑之變化檢測(cè)問(wèn)題(數(shù)組相關(guān)) ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
VUE寫(xiě)一個(gè)簡(jiǎn)單的表格實(shí)例
在本篇文章里小編給大家整理的是關(guān)于VUE中表格的寫(xiě)法實(shí)例以及相關(guān)知識(shí)點(diǎn)內(nèi)容,需要的朋友們可以參考下。2019-08-08

