vue實(shí)現(xiàn)壓縮圖片預(yù)覽并上傳功能(promise封裝)
本文實(shí)例為大家分享了vue實(shí)現(xiàn)壓縮圖片預(yù)覽并上傳的具體代碼,供大家參考,具體內(nèi)容如下
主要用到filereader、canvas 以及 formdata 這三個(gè)h5的api
過(guò)程大致分為三步:
用戶(hù)使用input file上傳圖片的時(shí)候,用filereader讀取用戶(hù)上傳的圖片數(shù)據(jù)(base64格式)
把圖片數(shù)據(jù)傳入img對(duì)象,然后將img繪制到canvas上,再調(diào)用canvas.toDataURL對(duì)圖片進(jìn)行壓縮
獲取到壓縮后的base64格式圖片數(shù)據(jù),轉(zhuǎn)成二進(jìn)制塞入formdata,再通過(guò)XmlHttpRequest提交formdata。
模板:
<template> <div class="image-box"> <input type="file" accept="image/*" @change="imageHandle"> <img ref="upImg"/> </div> </template>
獲取圖片數(shù)據(jù)
methods: {
//監(jiān)聽(tīng)input file的change事件
imageHandle(e) {
//**這個(gè)是必不可少的,在下面的reader.onload中this就不再指vm了**
let that = this;
let maxSize = 100 * 1024;
let files = e.srcElement.files;
if (!files.length) return; //文件長(zhǎng)度大于0
if (!/^image\//.test(files[0].type)) return; //必須是圖片才處理
if (!window.FileReader) return; //支持FileReader
//創(chuàng)建filereader對(duì)象
let reader = new FileReader();
reader.readAsDataURL(files[0]); //將圖片轉(zhuǎn)成base64格式
reader.onload = function() {
let result = this.result;
let img = new Image();
img.src = result;
let formdata = new FormData();
if (this.result.length <= maxSize) {
that.$refs.upImg.src = result; //預(yù)覽圖片
img = null;
//上傳圖片
formdata.append("image", that._upload(result, files[0].name, files[0].type));
that.$store.dispatch("uploadImage", formdata)
.then(data => {
if (data === 1) {
that.$toast("上傳成功", "success");
} else if (data === -1) {
that.$toast("圖片為空", "error");
} else {
that.$toast("上傳失敗", "error");
}
})
.catch(error => that.$toast("上傳失敗", "error"));
} else {
img.onload = function() {
//壓縮圖片
let data = that._compress(img);
//圖片預(yù)覽
that.$refs.upImg.src = data;
//上傳圖片
formdata.append("image", that._upload(data, files[0].name, files[0].type));
that.$store.dispatch("uploadImage", formdata)
.then(data => {
if (data === 1) {
that.$toast("上傳成功", "success");
} else if (data === -1) {
that.$toast("圖片為空", "error");
} else {
that.$toast("上傳失敗", "error");
}
})
.catch(error => that.$toast("上傳失敗", "error"));
};
}
};
},
壓縮圖片
在IOS中,canvas繪制圖片是有兩個(gè)限制的:
首先是圖片的大小,如果圖片的大小超過(guò)兩百萬(wàn)像素,圖片也是無(wú)法繪制到canvas上的,調(diào)用drawImage的時(shí)候不會(huì)報(bào)錯(cuò),但是你用toDataURL獲取圖片數(shù)據(jù)的時(shí)候獲取到的是空的圖片數(shù)據(jù)。
再者就是canvas的大小有限制,如果canvas的大小大于大概五百萬(wàn)像素(即寬高乘積)的時(shí)候,不僅圖片畫(huà)不出來(lái),其他什么東西也都是畫(huà)不出來(lái)的。
應(yīng)對(duì)第一種限制,處理辦法就是瓦片繪制了。瓦片繪制,也就是將圖片分割成多塊繪制到canvas上,我代碼里的做法是把圖片分割成100萬(wàn)像素一塊的大小,再繪制到canvas上。
而應(yīng)對(duì)第二種限制,我的處理辦法是對(duì)圖片的寬高進(jìn)行適當(dāng)壓縮,我代碼里為了保險(xiǎn)起見(jiàn),設(shè)的上限是四百萬(wàn)像素,如果圖片大于四百萬(wàn)像素就壓縮到小于四百萬(wàn)像素。四百萬(wàn)像素的圖片應(yīng)該夠了,算起來(lái)寬高都有2000X2000了。
如此一來(lái)就解決了IOS上的兩種限制了。
除了上面所述的限制,還有兩個(gè)坑,一個(gè)就是canvas的toDataURL是只能壓縮jpg的,當(dāng)用戶(hù)上傳的圖片是png的話(huà),就需要轉(zhuǎn)成jpg,也就是統(tǒng)一用canvas.toDataURL(‘image/jpeg', 0.1) , 類(lèi)型統(tǒng)一設(shè)成jpeg,而壓縮比就自己控制了。
另一個(gè)就是如果是png轉(zhuǎn)jpg,繪制到canvas上的時(shí)候,canvas存在透明區(qū)域的話(huà),當(dāng)轉(zhuǎn)成jpg的時(shí)候透明區(qū)域會(huì)變成黑色,因?yàn)閏anvas的透明像素默認(rèn)為rgba(0,0,0,0),所以轉(zhuǎn)成jpg就變成rgba(0,0,0,1)了,也就是透明背景會(huì)變成了黑色。解決辦法就是繪制之前在canvas上鋪一層白色的底色。
_compress(img) {
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
//瓦片
let tCanvas = document.createElement("canvas");
let tctx = tCanvas.getContext("2d");
let initSize = img.src.length;
let width = img.width;
let height = img.height;
//如果圖片大于四百萬(wàn)像素,計(jì)算壓縮比并將大小壓至400萬(wàn)以下
let ratio;
if ((ratio = (width * height) / 4000000) > 1) {
ratio = Math.sqrt(ratio);
widht /= ratio;
height /= ratio;
} else {
ratio = 1;
}
canvas.width = width;
canvas.height = height;
//鋪底色
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
//如果圖片像素大于100萬(wàn)則使用瓦片繪制
let count;
if ((count = (width * height) / 1000000) > 1) {
count = ~~(Math.sqrt(count) + 1); //計(jì)算要分成多少瓦片,~~在這里表示取整
//計(jì)算每塊瓦片的寬高
let nw = ~~(width / count);
let nh = ~~(height / count);
tCanvas.width = nw;
tCanvas.height = nh;
for (let i = 0; i < count; i++) {
for (let j = 0; j < count; j++) {
tctx.drawImage(
img, i * nw * ratio, j * nh * ratio, nw * ratio,nh * ratio, 0, 0, nw,nh
);
ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);
}
}
} else {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
}
//進(jìn)行壓縮
let ndata = canvas.toDataURL("image/jpeg", 0.3);
tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;
return ndata;
},
上傳
完成圖片壓縮后,就可以塞進(jìn)formdata里進(jìn)行上傳了,先將base64數(shù)據(jù)轉(zhuǎn)成字符串,再實(shí)例化一個(gè)ArrayBuffer,然后將字符串以8位整型的格式傳入ArrayBuffer,再通過(guò)BlobBuilder或者Blob對(duì)象,將8位整型的ArrayBuffer轉(zhuǎn)成二進(jìn)制對(duì)象blob,再將blob轉(zhuǎn)為File對(duì)象
_upload(data, name, type) {
let text = window.atob(data.split(",")[1]);
let buffer = new ArrayBuffer(text.length);
let ubuffer = new Uint8Array(buffer);
let pecent = 0,
loop = null;
for (var i = 0; i < text.length; i++) {
ubuffer[i] = text.charCodeAt(i);
}
let Builder =
window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder;
let blob;
if (Builder) {
var builder = new Builder();
builder.append(buffer);
blob = builder.getBlob(type);
} else {
blob = new window.Blob([ubuffer], { type: type });
}
// blob 轉(zhuǎn)file
var fileOfBlob = new File([blob], name, { type: type });
return fileOfBlob;
}
}
將圖片壓縮上傳封裝到一個(gè)js文件里
const UploadImg = {
imageHandle(files, maxSize, imgDom) {
let that = this;
let formdata = new FormData();
let reader = new FileReader();
reader.readAsDataURL(files[0]); //將圖片轉(zhuǎn)成base64格式
//reader.onload是異步,要用到Promise對(duì)象將值返回出去
return new Promise((resolved, rejected) => {
reader.onload = function () {
let result = this.result;
let img = new Image();
img.src = result;
if (this.result.length <= maxSize) {
imgDom.src = result;
img = null;
formdata.append("image", that._upload(result, files[0].name, files[0].type));
resolved(formdata);
} else {
img.onload = function () {
let data = that._compress(img);
imgDom.src = data;
formdata.append("image", that._upload(data, files[0].name, files[0].type));
resolved(formdata);
};
}
};
})
},
_compress(img) {
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
//瓦片
let tCanvas = document.createElement("canvas");
let tctx = tCanvas.getContext("2d");
let width = img.width;
let height = img.height;
//如果圖片大于四百萬(wàn)像素,計(jì)算壓縮比并將大小壓至400萬(wàn)以下
let ratio;
if ((ratio = (width * height) / 4000000) > 1) {
ratio = Math.sqrt(ratio);
widht /= ratio;
height /= ratio;
} else {
ratio = 1;
}
canvas.width = width;
canvas.height = height;
//鋪底色
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
//如果圖片像素大于100萬(wàn)則使用瓦片繪制
let count;
if ((count = (width * height) / 1000000) > 1) {
count = ~~(Math.sqrt(count) + 1); //計(jì)算要分成多少瓦片
//計(jì)算每塊瓦片的寬高
let nw = ~~(width / count);
let nh = ~~(height / count);
tCanvas.width = nw;
tCanvas.height = nh;
for (let i = 0; i < count; i++) {
for (let j = 0; j < count; j++) {
tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh);
ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);
}
}
} else {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
}
//進(jìn)行最小壓縮
let ndata = canvas.toDataURL("image/jpeg", 0.3);
tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;
return ndata;
},
_upload(data, name, type) {
let text = window.atob(data.split(",")[1]);
let buffer = new ArrayBuffer(text.length);
let ubuffer = new Uint8Array(buffer);
for (var i = 0; i < text.length; i++) {
ubuffer[i] = text.charCodeAt(i);
}
let Builder =
window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder;
let blob;
if (Builder) {
var builder = new Builder();
builder.append(buffer);
blob = builder.getBlob(type);
} else {
blob = new window.Blob([ubuffer], { type: type });
}
// blob 轉(zhuǎn)file
var fileOfBlob = new File([blob], name, { type: type });
return fileOfBlob;
}
}
export default UploadImg
調(diào)用代碼
import UploadImg from "../../util/uploadImg";
methods: {
imageHandle(e) {
let maxSize = 100 * 1024;
let imgDom = this.$refs.upImg;
let files = e.srcElement.files;
if (!files.length) return; //文件長(zhǎng)度大于0
if (!/^image\//.test(files[0].type)) return; //必須是圖片才處理
if (!window.FileReader) return; //支持FileReader
if (this.docEntry === "" || this.lineId === "") {
this.$toast("請(qǐng)?zhí)顚?xiě)完整信息", "error");
return;
}
// let formdata = new FormData();
UploadImg.imageHandle(files, maxSize, imgDom).then(formdata => {
formdata.append("docEntry", this.docEntry);
formdata.append("lineId", this.lineId);
formdata.append("action", "ProductionListImage");
this.$store
.dispatch("uploadImage", formdata)
.then(data => {
if (data === 1) {
this.$toast("上傳成功", "success");
} else if (data === -1) {
this.$toast("圖片為空", "error");
} else {
this.$toast("上傳失敗", "error");
}
})
.catch(error => this.$toast("上傳失敗", "error"));
});
}
}
參考鏈接:移動(dòng)端利用H5實(shí)現(xiàn)壓縮圖片上傳功能
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Vue計(jì)算屬性與監(jiān)視(偵聽(tīng))屬性的使用深度學(xué)習(xí)
這篇文章主要介紹了Vue計(jì)算屬性與監(jiān)視(偵聽(tīng))屬性的使用,計(jì)算屬性指的是通過(guò)一系列運(yùn)算之后,最終得到一個(gè)值,watch監(jiān)視(偵聽(tīng))器允許開(kāi)發(fā)者監(jiān)視數(shù)據(jù)的變化,從而針對(duì)數(shù)據(jù)的變化做特定的操作,本文就這兩種屬性給大家詳細(xì)講解,感興趣的朋友一起學(xué)習(xí)吧2022-11-11
詳解Vue組件實(shí)現(xiàn)tips的總結(jié)
這篇文章主要介紹了詳解Vue組件實(shí)現(xiàn)tips的總結(jié),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11
Vue中的數(shù)據(jù)驅(qū)動(dòng)解釋
這篇文章主要為大家介紹了Vue中的數(shù)據(jù)驅(qū)動(dòng)解釋?zhuān)行枰呐笥芽梢越梃b參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
使用vite構(gòu)建vue3項(xiàng)目的方法步驟
本文主要介紹了使用vite構(gòu)建vue3項(xiàng)目的方法步驟,vite支持性肯定比傳統(tǒng)的打包工具好,下面我們就來(lái)介紹一下使用vite構(gòu)建vue3項(xiàng)目,感興趣的可以參考一下2023-05-05
vue2.0 實(shí)現(xiàn)導(dǎo)航守衛(wèi)的具體用法(路由守衛(wèi))
這篇文章主要介紹了vue2.0 實(shí)現(xiàn)導(dǎo)航守衛(wèi)的具體用法(路由守衛(wèi)),vue-route 提供的 beforeRouteUpdate 可以方便地實(shí)現(xiàn)導(dǎo)航守衛(wèi)(navigation-guards),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
vue第三方庫(kù)中存在擴(kuò)展運(yùn)算符報(bào)錯(cuò)問(wèn)題的解決方案
這篇文章主要介紹了vue第三方庫(kù)中存在擴(kuò)展運(yùn)算符報(bào)錯(cuò)問(wèn)題,本文給大家分享解決方案,通過(guò)結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
關(guān)于Vue 自定義指令實(shí)現(xiàn)元素拖動(dòng)的詳細(xì)代碼
這篇文章主要介紹了Vue 自定義指令實(shí)現(xiàn)元素拖動(dòng),在使用自定義指令之前,先對(duì)自定義指令有一定的了解,主要從自定義指令定義范圍,鉤子函數(shù)方面入手,需要的朋友可以參考下2022-01-01

