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)回車事件
- 獲取可編輯div容器中的所有子元素
- 遍歷獲取到的元素,找出img元素
- 判斷當(dāng)前img元素是否有alt屬性(表情插入時(shí)有alt屬性),
- 如果沒(méi)有alt屬性當(dāng)前元素就是圖片
- 將base64格式的圖片轉(zhuǎn)成文件上傳至服務(wù)器
- 上傳成功后,將服務(wù)器返回的圖片地址推送到websocket服務(wù)
- 客戶端收到推送后,渲染頁(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) {
// 自己寫的一個(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中的圖片名稱
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)文章
解決創(chuàng)建vue項(xiàng)目后沒(méi)有vue.config.js文件的問(wèn)題
這篇文章給大家主要介紹如何解決創(chuàng)建vue項(xiàng)目后沒(méi)有webpack.config.js(vue.config.js)文件,文中有詳細(xì)的解決方法,感興趣的朋友可以參考閱讀下2023-07-07
vue+springboot實(shí)現(xiàn)項(xiàng)目的CORS跨域請(qǐng)求
這篇文章主要介紹了vue+springboot實(shí)現(xiàn)項(xiàng)目的CORS跨域請(qǐng)求,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
Vue使用Axios庫(kù)請(qǐng)求數(shù)據(jù)時(shí)跨域問(wèn)題的解決方法詳解
在 VUE 項(xiàng)目開(kāi)發(fā)時(shí),遇到個(gè)問(wèn)題,正常設(shè)置使用 Axios 庫(kù)請(qǐng)求數(shù)據(jù)時(shí),報(bào)錯(cuò)提示跨域問(wèn)題,那在生產(chǎn)壞境下,該去怎么解決呢?下面小編就來(lái)和大家詳細(xì)講講2024-01-01
如何用Vite構(gòu)建工具快速創(chuàng)建Vue項(xiàng)目
Vite是一個(gè)web開(kāi)發(fā)構(gòu)建工具,由于其原生?ES?模塊導(dǎo)入方法,它允許快速提供代碼,下面這篇文章主要給大家介紹了關(guān)于如何用Vite構(gòu)建工具快速創(chuàng)建Vue項(xiàng)目的相關(guān)資料,需要的朋友可以參考下2022-05-05
vue用elementui寫form表單時(shí),在label里添加空格操作
這篇文章主要介紹了vue用elementui寫form表單時(shí),在label里添加空格操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
解決vue-cli中stylus無(wú)法使用的問(wèn)題方法
這篇文章主要介紹了解決vue-cli中stylus無(wú)法使用的問(wèn)題方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06

