/** * 图片压缩工具 * 用于压缩大于1MB的图片到0.8MB-1MB之间,保持原文件格式 */ /** * 压缩图片到目标大小范围 * @param file - 原始图片文件 * @param targetMinSize - 目标最小大小(字节) * @param targetMaxSize - 目标最大大小(字节) * @param quality - 压缩质量(0-1),默认0.9 * @returns Promise - 压缩后的文件 */ async function compressImageToTargetSize( file: File, targetMinSize: number, targetMaxSize: number, quality: number = 0.9 ): Promise { return new Promise((resolve, reject) => { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') const img = new Image() img.onload = () => { try { const originalWidth = img.width const originalHeight = img.height // 先尝试保持原尺寸,只调整质量 canvas.width = originalWidth canvas.height = originalHeight if (ctx) { ctx.imageSmoothingEnabled = true ctx.imageSmoothingQuality = 'high' } // 绘制原始尺寸的图片 ctx?.drawImage(img, 0, 0, originalWidth, originalHeight) // 转换为blob canvas.toBlob( (blob) => { if (!blob) { reject(new Error('图片压缩失败')) return } // 检查压缩结果是否在目标范围内 if (blob.size >= targetMinSize && blob.size <= targetMaxSize) { const compressedFile = new File([blob], file.name, { type: file.type, lastModified: Date.now() }) resolve(compressedFile) } else if (blob.size > targetMaxSize) { // 如果还是太大,需要调整尺寸 compressWithSizeAdjustment(file, img, targetMinSize, targetMaxSize, quality, resolve, reject) } else { // 如果太小了,提高质量重新压缩 compressWithHigherQuality(file, img, targetMinSize, targetMaxSize, quality, resolve, reject) } }, file.type, quality ) } catch (error) { reject(new Error('图片压缩过程中发生错误: ' + error.message)) } } img.onerror = () => { reject(new Error('图片加载失败')) } // 创建图片URL const reader = new FileReader() reader.onload = (e) => { img.src = e.target?.result as string } reader.onerror = () => { reject(new Error('文件读取失败')) } reader.readAsDataURL(file) }) } /** * 通过调整尺寸进行压缩 */ function compressWithSizeAdjustment( file: File, img: HTMLImageElement, targetMinSize: number, targetMaxSize: number, quality: number, resolve: (file: File) => void, reject: (error: Error) => void ) { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') const originalWidth = img.width const originalHeight = img.height // 计算目标尺寸,使用更保守的缩放策略 const targetSize = (targetMinSize + targetMaxSize) / 2 const sizeRatio = targetSize / file.size // 使用更温和的缩放比例,避免过度压缩 let scale = Math.sqrt(sizeRatio) * 1.2 // 比理论值稍大一些,避免过度压缩 // 确保缩放比例在合理范围内 scale = Math.max(0.5, Math.min(1.0, scale)) const targetWidth = Math.floor(originalWidth * scale) const targetHeight = Math.floor(originalHeight * scale) canvas.width = targetWidth canvas.height = targetHeight if (ctx) { ctx.imageSmoothingEnabled = true ctx.imageSmoothingQuality = 'high' } // 绘制缩放后的图片 ctx?.drawImage(img, 0, 0, targetWidth, targetHeight) canvas.toBlob( (blob) => { if (!blob) { reject(new Error('图片压缩失败')) return } const compressedFile = new File([blob], file.name, { type: file.type, lastModified: Date.now() }) // 检查结果,如果还是太大,继续调整 if (blob.size > targetMaxSize && scale > 0.5) { // 递归调用,进一步缩小 const newScale = scale * 0.9 const newWidth = Math.floor(originalWidth * newScale) const newHeight = Math.floor(originalHeight * newScale) canvas.width = newWidth canvas.height = newHeight ctx?.drawImage(img, 0, 0, newWidth, newHeight) canvas.toBlob( (newBlob) => { if (newBlob) { const newFile = new File([newBlob], file.name, { type: file.type, lastModified: Date.now() }) resolve(newFile) } else { resolve(compressedFile) // 如果二次压缩失败,返回第一次的结果 } }, file.type, quality ) } else { resolve(compressedFile) } }, file.type, quality ) } /** * 通过提高质量重新压缩(当压缩结果太小时) */ function compressWithHigherQuality( file: File, img: HTMLImageElement, targetMinSize: number, targetMaxSize: number, currentQuality: number, resolve: (file: File) => void, reject: (error: Error) => void ) { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') canvas.width = img.width canvas.height = img.height if (ctx) { ctx.imageSmoothingEnabled = true ctx.imageSmoothingQuality = 'high' } // 提高质量,但不超过0.95 const newQuality = Math.min(0.95, currentQuality + 0.1) ctx?.drawImage(img, 0, 0, img.width, img.height) canvas.toBlob( (blob) => { if (!blob) { reject(new Error('图片压缩失败')) return } const compressedFile = new File([blob], file.name, { type: file.type, lastModified: Date.now() }) // 如果提高质量后仍然太小,或者已经达到最高质量,直接返回 if (blob.size < targetMinSize && newQuality < 0.95) { compressWithHigherQuality(file, img, targetMinSize, targetMaxSize, newQuality, resolve, reject) } else { resolve(compressedFile) } }, file.type, newQuality ) } /** * 压缩图片 * @param file - 原始图片文件 * @param maxSize - 最大文件大小(字节),默认1MB * @param quality - 压缩质量(0-1),默认0.9 * @returns Promise - 压缩后的文件 */ export async function compressImage( file: File, maxSize: number = 1 * 1024 * 1024, quality: number = 0.9 ): Promise { // 如果文件小于等于最大限制,直接返回原文件 if (file.size <= maxSize) { return file } // 对于大于1MB的文件,设置目标大小为0.8MB-1MB之间 const targetMinSize = 800 * 1024 // 800KB const targetMaxSize = maxSize // 1MB return compressImageToTargetSize(file, targetMinSize, targetMaxSize, quality) } /** * 批量压缩图片 * @param files - 图片文件数组 * @param maxSize - 最大文件大小(字节),默认1MB * @param quality - 压缩质量(0-1),默认0.8 * @returns Promise - 压缩后的文件数组 */ export async function compressImages( files: File[], maxSize: number = 1 * 1024 * 1024, quality: number = 0.8 ): Promise { const results: File[] = [] for (const file of files) { try { const compressedFile = await compressImage(file, maxSize, quality) results.push(compressedFile) } catch (error) { console.error(`压缩文件 ${file.name} 失败:`, error) // 压缩失败时保留原文件 results.push(file) } } return results } /** * 获取图片信息 * @param file - 图片文件 * @returns Promise<{width: number, height: number, size: number, type: string}> */ export function getImageInfo(file: File): Promise<{ width: number height: number size: number type: string }> { return new Promise((resolve, reject) => { const img = new Image() img.onload = () => { resolve({ width: img.width, height: img.height, size: file.size, type: file.type }) } img.onerror = () => { reject(new Error('无法获取图片信息')) } const reader = new FileReader() reader.onload = (e) => { img.src = e.target?.result as string } reader.onerror = () => { reject(new Error('文件读取失败')) } reader.readAsDataURL(file) }) }