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

實現(xiàn)思路
- 頁面掛載時監(jiān)聽剪切板粘貼事件
- 監(jiān)聽文件流
- 讀取文件流中的數(shù)據(jù)
- 創(chuàng)建img標簽
- 將獲取到的base64碼賦值到img標簽的src屬性
- 將生成的img標簽append到即將發(fā)送的消息容器里
- 監(jiān)聽回車事件
- 獲取可編輯div容器中的所有子元素
- 遍歷獲取到的元素,找出img元素
- 判斷當前img元素是否有alt屬性(表情插入時有alt屬性),
- 如果沒有alt屬性當前元素就是圖片
- 將base64格式的圖片轉成文件上傳至服務器
- 上傳成功后,將服務器返回的圖片地址推送到websocket服務
- 客戶端收到推送后,渲染頁面
實現(xiàn)過程
本片文章主要講解剪切板圖片的解析以及將base64圖片轉換成文件上傳至服務器,下方代碼中的axios的封裝以及websocket的配置與使用可參考我的另外兩篇文章:Vue合理配置axios并在項目中進行實際應用和Vue合理配置WebSocket并實現(xiàn)群聊
監(jiān)聽剪切板事件(mounted生命周期中),將圖片渲染到即將發(fā)送到消息容器里
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;
// 創(chuàng)建img標簽
let img = document.createElement('img');//創(chuàng)建一個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圖片壓縮函數(shù)
// 參數(shù): base64地址,壓縮比例,回調函數(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");
// 當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");
}
}
}
完善消息發(fā)送函數(shù),獲取輸入框里的所有子元素,找出base64圖片將其轉為文件并上傳至服務器(此處需要注意:base64轉文件時,需要用正則表達式刪掉base64圖片的前綴),將當前圖片地址推送至websocket服務。
對下述代碼有不理解的地方,可閱讀我的另一篇文章:Vue實現(xiàn)圖片與文字混輸,
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}/`;
// 消息發(fā)送: 發(fā)送圖片
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節(jié)點的值
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
});
// 清空輸入框中的內容
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) {
// 解析接口返回的數(shù)據(jù)并進行渲染
let separateReg = /(\/[^/]+\/)/g;
let msgText = msgObj.msgText;
let finalMsgText = "";
// 將符合條件的字符串放到數(shù)組里
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發(fā)送至服務端
結果很明顯,服務端websocket服務報錯,報錯原因:內容超過最大長度。
前端通過post請求將base64碼傳到服務端,服務端直接將base64碼解析為圖片保存至服務器
從下午2點折騰到晚上6點,一直在找Java解析base64圖片存到服務器的方案,最終選擇了放棄,采用了前端轉換方式,這里的問題大概是前端傳base64碼到后端時,http請求會進行轉義,導致后端解析得到的base64碼是錯誤的,所以一直沒有成功。
項目地址:chat-system
總結
以上所述是小編給大家介紹的Vue解析剪切板圖片并實現(xiàn)發(fā)送功能,希望對大家有所幫助!
相關文章
vue項目環(huán)境搭建?啟動?移植操作示例及目錄結構分析
這篇文章主要介紹了vue項目環(huán)境搭建、啟動、項目移植、項目目錄結構分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04
Electron主進程(Main?Process)與渲染進程(Renderer?Process)通信詳解
這篇文章主要介紹了Electron主進程(Main?Process)與渲染進程(Renderer?Process)通信,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
Vue?2?和?Vue?3?中?toRefs函數(shù)的不用用法
Vue?是一款流行的JavaScript?框架,用于構建用戶界面,在Vue2和?Vue3中,都存在一個名為toRefs的函數(shù),但其行為在這兩個版本中有所不同,這篇文章主要介紹了Vue2和Vue3中toRefs的區(qū)別,需要的朋友可以參考下2023-08-08

