/** * 前端图片压缩工具 * 在浏览器中压缩图片,减少上传数据量,提高处理速度 */ interface CompressOptions { maxWidth?: number maxHeight?: number quality?: number format?: 'image/jpeg' | 'image/webp' | 'image/png' } /** * 压缩图片文件 * @param file 原始图片文件 * @param options 压缩选项 * @returns 压缩后的 Blob */ export async function compressImage( file: File, options: CompressOptions = {} ): Promise { const { maxWidth = 800, maxHeight = 2000, quality = 0.85, format = 'image/jpeg', } = options return new Promise((resolve, reject) => { const img = new Image() const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') if (!ctx) { reject(new Error('无法创建 Canvas 上下文')) return } img.onload = () => { try { // 计算新尺寸(保持比例) let { width, height } = img // 如果图片尺寸已经在限制内,且文件不大,直接返回 if (width <= maxWidth && height <= maxHeight && file.size < 500 * 1024) { // ⚠️ 内存泄漏修复:释放 Object URL(R20) URL.revokeObjectURL(img.src) resolve(file) return } // 计算缩放比例 let scale = 1 if (width > maxWidth) { scale = Math.min(scale, maxWidth / width) } if (height > maxHeight) { scale = Math.min(scale, maxHeight / height) } const newWidth = Math.round(width * scale) const newHeight = Math.round(height * scale) // 设置 canvas 尺寸 canvas.width = newWidth canvas.height = newHeight // 绘制图片(使用高质量缩放) ctx.imageSmoothingEnabled = true ctx.imageSmoothingQuality = 'high' ctx.drawImage(img, 0, 0, newWidth, newHeight) // 转换为 Blob canvas.toBlob( (blob) => { // ⚠️ 内存泄漏修复:处理完成后释放 Object URL(R20) URL.revokeObjectURL(img.src) if (blob) { console.log( `[前端压缩] ${file.name}: ${(file.size / 1024).toFixed(2)}KB -> ${(blob.size / 1024).toFixed(2)}KB (${width}x${height} -> ${newWidth}x${newHeight})` ) resolve(blob) } else { reject(new Error('图片压缩失败')) } }, format, quality ) } catch (error) { // ⚠️ 内存泄漏修复:错误时也要释放 Object URL(R20) URL.revokeObjectURL(img.src) reject(error) } } img.onerror = () => { // ⚠️ 内存泄漏修复:加载失败时释放 Object URL(R20) URL.revokeObjectURL(img.src) reject(new Error('图片加载失败')) } // 加载图片 img.src = URL.createObjectURL(file) }) } /** * 将 Blob 转换为 File 对象 */ export function blobToFile(blob: Blob, fileName: string): File { return new File([blob], fileName, { type: blob.type }) } /** * 批量压缩图片 */ export async function compressImages( files: File[], options: CompressOptions = {} ): Promise { const compressedFiles: File[] = [] for (let i = 0; i < files.length; i++) { const file = files[i] try { const compressedBlob = await compressImage(file, options) const compressedFile = blobToFile(compressedBlob, file.name) compressedFiles.push(compressedFile) } catch (error) { console.error(`[前端压缩] 压缩 ${file.name} 失败:`, error) // 压缩失败时使用原文件 compressedFiles.push(file) } } return compressedFiles }