项目场景:
采用uniapp后台框架uniAdimin开发运营管理系统,需要同时支持上传图片和视频,前端实现图片上传前压缩以及大小限制提示。
解决方案:
采用 uni.chooseFile 配置 extension属性值 达到同时上传文件和视频的目的,而不是用 uni.chooseImage 只能上传图片。
// 选择图片
chooseImage(){
uni.chooseImage({
count: this.canLength < this.count ? this.canLength : this.count, //最多可以选择的图片张数,默认9
sizeType: ['original', 'compressed'], //original 原图,compressed 压缩图,默认二者都有
sourceType: ['album', 'camera'], //album 从相册选图,camera 使用相机,默认二者都有
success: res=> {
console.log(res.tempFilePaths);
this.uploadImage(res.tempFilePaths);
}
});
},
// 选择文件
chooseFile(){
let self = this;
uni.chooseFile({
count: this.canLength < this.count ? this.canLength : this.count,
extension:['.png','.jpg','.gif','.mp4'],
success: res=> {
console.log(res.tempFiles);
let file = res.tempFiles[0];
if(file.type==='video/mp4'){
if(file.size>1024*1024*200){
uni.showToast({
title:'上传视频大小不能超过200M',
icon: 'none'
})
}else{
this.uploadFiles([file]);
}
}else{
this.compressImage(file);
}
}
});
},
从上面代码可以看出,选择文件时我们支持png、jpg、gif、mp4四种格式,判断上传内容为视频时进行了大小限制200M,图片类型则进入压缩逻辑。
图片压缩方案一:
采用
helang-compress 组件压缩图片,插件地址及使用方法如下:
图片压缩(自定义 压缩尺寸&压缩质量) – DCloud 插件市场
helangCompress(file){
this.$refs.helangCompress.compress({
src: file.path,
fileType: 'jpg',
quality: .5
}).then((comRes)=>{
console.log(comRes)
}).catch((err)=>{
console.log(err)
})
},
遇到的问题:该压缩方式返回的是base64格式的编码,而在使用uniCloud.uploadFile上传图片时并不支持base64编码格式。
图片压缩方案二:
采用
canvas 绘图的方
式压缩
图片,使用方法如下:
compressImage(file){
let self = this;
var image = new Image();
image.src = file.path;
image.onload = function(){
// 创建canvas
var canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
// 获取上下文
var context = canvas.getContext('2d');
// 把图片绘制到canvas上面
context.drawImage(image, 0, 0, canvas.width, canvas.height);
// 压缩图片 获取到新的base64
var base64Data = canvas.toDataURL(file.type, .2);
// console.log(base64Data)
// 将base64转为file文件流
// var resultFile = self.dataURLtoFile(base64Data,file.name);
// console.log(resultFile)
// 将base64转为blob后上传
let base64 = base64Data.split(',')[1];
self.base64ToBlob({
b64data: base64,
contentType: file.type,
contentName: file.name,
}).then(blobRes => {
console.log(blobRes)
if(blobRes.size>1024*1024*2){
uni.showToast({
title:'上传图片压缩后不能超过2M',
icon: 'none'
})
}else{
console.log([blobRes])
self.uploadImage([blobRes]);
}
})
}
},
遇到的问题:该方式压缩返回的依然是base64格式,不知道如何上传。对比uni.chooseFile获取到的原始文件格式,发现是file文件流形式,且path属性值为blob协议地址。
base64转file
这时候想到的是将base64转化为和它同样的file文件流形式然后再上传,但是将成功转后的file对象直接上传还是不行,排查原因发现我们手动转后的file文件流并不含path属性。
// base64转文件流 实测转后无path
dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1];
var bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {type:mime});
},
base64转blob
对比发现差一个path属性,再次尝试将base64转为blob协议的形式直接上传,代码使用如下:
// base64转bolb 其原理是利用URL.createObjectURL为blob对象创建临时的URL
base64ToBlob ({b64data = '', contentType = '', contentName = '', sliceSize = 512} = {}) {
return new Promise((resolve, reject) => {
// 使用 atob() 方法将数据解码
let byteCharacters = atob(b64data);
let byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
let slice = byteCharacters.slice(offset, offset + sliceSize);
let byteNumbers = [];
for (let i = 0; i < slice.length; i++) {
byteNumbers.push(slice.charCodeAt(i));
}
byteArrays.push(new Uint8Array(byteNumbers));
}
let result = new Blob(byteArrays, {
type: contentType
})
result = Object.assign(result,{
preview: URL.createObjectURL(result),
fileName: contentName
});
resolve(result)
})
},
// base64转blob
base64toBlob(dataUrl, sliceSize = 512) {
const fileType = dataUrl.split(';')[0].split(':')[1]; // 文件类型
const base64Data = dataUrl.split(',')[1]; // 数据
const byteCharacters = atob(base64Data);
const byteArrays = [];
for (
let offset = 0;
offset < byteCharacters.length;
offset += sliceSize
) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, { type: fileType });
return blob;
},
发现可以直接拿到blob协议地址,也就是上面原始文件path后面那一串,这个协议指向的应该本地的文件对象,对比 uni.chooseImage 选择图片上传时,拿到的也是这个地址而非file文件流,所以计划针对图片直接使用转后的blob上传,最终测试上传成功。
上传结果分析:
针对图片,在保持尺寸不变且不失真的情况下,体积我们可以做到10:1,也就是10M的图片我们可以压到1M左右,接下来我们一起来看看结果对比图。
对比发现,压缩质量为0.2时,可以做到最佳效果,质量再低的话图片会出现明显肉眼可见模糊。