- 微信小程序处理图片
-
图片的压缩处理,可以是后端处理,也可以前端处理。哪为啥上传需要前端处理,下载需要后端处理呢?
因为,涉及到流量。上传图片太大、下载图片太大,那么流量消耗就大。所以上传需要前端做处理。
-
前端如何实现根据上传的图片大小适当压缩呢?
如:上传20K的图片无需压缩,上传10M的压缩成2M,上传4M的图片,也压缩成2M。这个逻辑如何实现?
-
通过一个递归调用,来判断图片是否小于2M,如果满足则提交该图片。如下代码(compressImage.ts):
import Taro from '@tarojs/taro';
export type compressImageResult = {
resultCode: string;
filePath?: string;
errorDesc?: string;
};
/**
*参数
* @param oldFilePath 原图地址
* @param filePath 压缩后的图片地址
* @param limitSize 图片大小kb
* @param quality 图片质量
* @param step 图片质量每次降低多少
* @param callback 回调
*/
export type compressImageParams = {
oldFilePath: string;
filePath?: string;
limitSize?: number;
quality?: number;
step?: number;
callback: (res: compressImageResult) => void;
};
export const compressImage = (params: compressImageParams) => {
if (!params.limitSize) {
params.limitSize = 2048; // 2M 大小
}
if (!params.quality) {
params.quality = 70; // 图片质量 默认70
}
if (!params.step) {
params.step = 5; // 图片质量每次降低多少
}
const path = params.filePath ?? params.oldFilePath!;
Taro.getFileSystemManager().getFileInfo({
filePath: path,
success: async res => {
if (res.size <= 1024 * 1024 * 10) {
params.callback({
resultCode: 'FAIL',
filePath: '',
errorDesc: '图片超过了10M'
});
return;
}
// console.log(`图片压缩size:${res.size / 1024}kb`, `quality:${params.quality}`);
if (res.size > 1024 * params.limitSize!) {
Taro.compressImage({
src: params.oldFilePath!,
quality: params.quality! - params.step!,
success(result: Taro.compressImage.SuccessCallbackResult) {
const data: compressImageParams = {
oldFilePath: params.oldFilePath!,
filePath: result.tempFilePath,
limitSize: params.limitSize,
quality: params.quality! - params.step!,
step: params.step,
callback: params.callback
};
compressImage(data);
}
});
} else {
const newPath = params.filePath ?? params.oldFilePath!;
// console.log(`压缩成功!size:${res.size / 1024}kb`, `quality:${params.quality}`, `path:${newPath}`);
// const base64 = Taro.getFileSystemManager().readFileSync(filePath);
params.callback({
resultCode: 'SUCCESS',
filePath: newPath,
errorDesc: ''
});
}
},
fail(res) {
params.callback({
resultCode: 'FAIL',
filePath: '',
errorDesc: res.errMsg
});
}
});
};
- H5处理图片
- 上一章节图片处理,使用的是Taro默认的相关API。但是不支持H5?
-
H5对于图片的处理,还是要请出canvas对象。实现的逻辑跟上一章节一样,使用递归的逻辑,使图片满足大小要求。
-
如下代码(compressImage.ts):
//压缩
const RESULT_ENUM = {
ORIGIN: 'ORIGIN', // 无需压缩,文件符合大小
SUCCESS: 'SUCCESS', // 压缩成功
FAIL: 'FAIL', // 压缩失败, 无法压缩到指定大小
};
//图片压缩
export async function compressImg(
file: File | undefined,
maxSize: number,
reduceCount = 0,
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const fileBase64 = await getBase64WithFile(file);
// console.log(fileBase64,"fileBase64")
const img = await base64ToImage(fileBase64);
// console.log(img,"img")
// 用canvas绘制
let scale = 1;
if (reduceCount > 0) {
scale = 1 / Math.pow(1.2, reduceCount);
}
const originWidth = img.width;
const originHeight = img.height;
const targetWidth = originWidth * scale;
const targetHeight = originHeight * scale;
canvas.width = targetWidth;
canvas.height = targetHeight;
ctx?.clearRect(0, 0, targetWidth, targetHeight);
ctx?.drawImage(img, 0, 0, targetWidth, targetHeight);
const originBlob = await canvastoBlob(canvas, 'image/jpeg', 1);
const originSize = originBlob.size / 1024;
console.log(originBlob, originSize, maxSize, 12321);
if (originSize <= maxSize) {
return {
msg: RESULT_ENUM.ORIGIN,
base64: canvas.toDataURL('image/jpeg', 1),
};
}
const maxQualitySize = { quality: 100, size: Number.MAX_SAFE_INTEGER };
const minQualitySize = { quality: 0, size: 0 };
let quality = 100;
let count = 0; // 压缩次数
let compressFinish = false; // 压缩完成
let compressFail = false;
let compressBlob;
let needReduce = false; // 递归压缩
// 二分法最多尝试8次即可覆盖全部可能
while (!compressFinish && count < 12) {
const currentQuality = quality / 100;
compressBlob = await canvastoBlob(canvas, 'image/jpeg', currentQuality);
const compressSize = compressBlob.size / 1024;
count++;
if (compressSize === maxSize) {
console.log(`压缩完成,总共压缩了${count}次`);
compressFinish = true;
const base64 = canvas.toDataURL('image/jpeg', currentQuality);
return {
msg: RESULT_ENUM.SUCCESS,
base64,
};
}
if (compressSize > maxSize) {
maxQualitySize.quality = quality;
maxQualitySize.size = compressSize;
}
if (compressSize < maxSize) {
minQualitySize.quality = quality;
minQualitySize.size = compressSize;
}
console.log(
`第${count}次压缩,压缩后大小${compressSize},quality参数:${quality}`,
);
quality = Math.ceil((maxQualitySize.quality + minQualitySize.quality) / 2);
if (maxQualitySize.quality - minQualitySize.quality < 2) {
console.log({ minQualitySize, quality });
if (!minQualitySize.size && quality) {
quality = minQualitySize.quality;
} else if (!minQualitySize.size && !quality) {
if (quality === 0) {
needReduce = true;
}
compressFinish = true;
compressFail = true;
console.log(`压缩完成,总共压缩了${count}次`);
} else if (minQualitySize.size > maxSize) {
compressFinish = true;
compressFail = true;
needReduce = true;
console.log(`压缩完成,总共压缩了${count}次`);
} else {
console.log(`压缩完成,总共压缩了${count}次`);
compressFinish = true;
quality = minQualitySize.quality;
}
}
}
// 递归
if (needReduce) {
console.log(1, maxSize, reduceCount);
const nextReduceBlob = await canvastoBlob(canvas, 'image/jpeg', 1 / 100);
const reduceFile = generateFileFromBlob(nextReduceBlob);
return await compressImg(reduceFile, maxSize, reduceCount + 1);
} else if (compressFail) {
console.log(2);
return {
msg: RESULT_ENUM.FAIL,
base64: fileBase64,
};
}
const currentQuality = quality / 100;
compressBlob = await canvastoBlob(canvas, 'image/jpeg', currentQuality);
const compressSize = compressBlob.size / 1024;
console.log(
`最后一次压缩(即第${
count + 1
}次),quality为:${quality},大小:${compressSize}`,
);
const base64 = canvas.toDataURL('image/jpeg', currentQuality);
return {
msg: RESULT_ENUM.SUCCESS,
base64,
};
}
export function generateFileFromBlob(
compressBlob,
fileName = new Date().getTime().toString(),
) {
return new File([compressBlob], fileName, {
type: 'image/jpeg',
});
}
// canvas转成blob
export function canvastoBlob(canvas, type, quality): Promise<Blob> {
return new Promise((resolve) =>
canvas.toBlob((blob) => resolve(blob as Blob), type, quality),
);
}
// file对象转base64
export function getBase64WithFile(file): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = (error) => reject(error);
});
}
// base64转成image
export function base64ToImage(dataURL): Promise<HTMLImageElement> {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(img);
img.src = dataURL;
});
}
export const dataURLtoBlob = (dataurl) => {
const arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
};