/**
* 压缩base64图片的函数
*
* @param {string} base64 - 图片的base64编码
* @param {number} multiple - 缩放比例
* @param {number} quality - 输出图片的质量(0.0到1.0之间)
* @param {string} format - 输出图片的格式,默认为"image/webp" 可选参数 image/png、image/jpeg、image/webp 等
* @param {number} min - 压缩后图片的最小KB
* @param {number} max - 压缩后图片的最大KB
* @timeout {number} timeout - 压缩图片最大等待时长,如果超时返回最后一次压缩数据
* @returns {Promise<string>} - 返回压缩后的图片的base64编码
*/
export function compressImg(
base64,
multiple = 1,
quality = 1,
format = "image/webp",
min,
max,
timeout = 1000
) {
return new Promise((resolve, reject) => {
if (!base64) return resolve(base64);
const startTime = Date.now();
const timeoutThreshold = startTime + timeout;
let isCompleted = false;
const finalize = (result, error = null) => {
if (isCompleted) return;
isCompleted = true;
cleanup();
error ? reject(error) : resolve(result);
};
const cleanup = () => {
clearTimeout(loadTimer);
img.onload = null;
img.onerror = null;
canvas = null;
};
const createStepGenerator = (target, currentSize, isMax) => {
const sizeDiff = Math.abs(target - currentSize);
const initialStep = sizeDiff > 100 ? 0.1 :
sizeDiff > 50 ? 0.05 :
sizeDiff > 10 ? 0.02 : 0.01;
return {
current: initialStep,
next() {
this.current = Math.max(
this.current * (isMax ? 0.8 : 1.2),
0.005
);
return this.current;
}
};
};
const optimizeQuality = (initialQ, targetKB, isMax) => {
let q = Math.min(Math.max(initialQ, 0.1), 0.95);
let bestQ = q;
let bestSize = getSizeForQuality(q);
const stepGen = createStepGenerator(targetKB, bestSize, isMax);
let direction = isMax ? -1 : 1;
while (true) {
if (Date.now() > timeoutThreshold) return bestQ;
const currentSize = getSizeForQuality(q);
if (currentSize === undefined) return bestQ;
if ((isMax && currentSize <= targetKB) ||
(!isMax && currentSize >= targetKB)) {
if ((isMax && q > bestQ) || (!isMax && q < bestQ)) {
bestQ = q;
bestSize = currentSize;
}
}
if ((isMax && currentSize > targetKB) ||
(!isMax && currentSize < targetKB)) {
q += direction * stepGen.next();
} else {
q += direction * stepGen.current;
}
if (q > 0.95 || q < 0.1) {
direction *= -0.5;
q = Math.min(Math.max(q, 0.1), 0.95);
}
if (Math.abs(bestSize - targetKB) < 2 ||
stepGen.current < 0.006) {
return bestQ;
}
}
};
const getSizeForQuality = (q) => {
if (Date.now() > timeoutThreshold) return;
try {
return canvas.toDataURL(validFormat, q).length / 1024;
} catch {
return Infinity;
}
};
const img = new Image();
let canvas;
let validFormat = [...new Set([format, "image/jpeg", "image/png"])]
.find(f => document.createElement("canvas").toDataURL(f) !== "data:,");
const loadTimer = setTimeout(() => {
finalize(null, new Error("Image load timeout"));
}, timeout - 50);
img.crossOrigin = "Anonymous";
img.onerror = () => finalize(null, new Error("Image load failed"));
img.src = base64;
img.onload = function () {
try {
if (Date.now() > timeoutThreshold) {
return finalize(null, new Error("Operation timeout"));
}
const width = img.width * multiple;
const height = img.height * multiple;
canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
canvas.getContext("2d").drawImage(img, 0, 0, width, height);
let optimizedQuality = quality;
let finalDataUrl;
// 获取初始质量对应的文件大小
const initialSize = getSizeForQuality(quality);
// 优先处理max限制(基于调整后的质量)
if (typeof max === "number" && initialSize > max) {
optimizedQuality = optimizeQuality(optimizedQuality, max, true);
}
// 处理min限制
if (typeof min === "number" && initialSize < min) {
optimizedQuality = optimizeQuality(optimizedQuality, min, false);
}
finalDataUrl = canvas.toDataURL(
validFormat,
Math.min(Math.max(optimizedQuality, 0.1), 0.95)
);
finalize(finalDataUrl);
} catch (error) {
finalize(null, error);
}
};
});
}
使用: const result = await compressImg(base64, 1, .8, "image/webp", 0, 1024,1000)
js 压缩base64图片 可设置缩放比例、质量、格式、大小、超时时间
于 2025-05-08 11:48:25 首次发布