uniAdmin框架H5前端上传图片压缩问题

  • Post author:
  • Post category:其他




项目场景:


采用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时,可以做到最佳效果,质量再低的话图片会出现明显肉眼可见模糊。



版权声明:本文为zd1007129657原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。