diff --git a/doc/compressorjs-guide.md b/doc/compressorjs-guide.md new file mode 100644 index 0000000..85560c2 --- /dev/null +++ b/doc/compressorjs-guide.md @@ -0,0 +1,318 @@ +# CompressorJS 实战使用文档 + +基于项目中的 `src/pages/approval/submit.vue` 实例编写。 + +## 1. 简介 + +CompressorJS 是一个轻量级的 JavaScript 图像压缩库,基于 Canvas API,依赖少且易用。适用于文件上传前压缩,可显著减少文件大小和上传时间。 + +## 2. 安装 + +```bash +pnpm add compressorjs +# 或 +npm install compressorjs +``` + +## 3. 基础使用(基于项目实例) + +### 3.1 基本语法 + +```javascript +import Compressor from 'compressorjs' + +new Compressor(file, { + quality: 0.8, + maxWidth: 1280, + maxHeight: 1280, + success(result) { + // 压缩成功后的处理 + }, + error(err) { + // 压缩失败处理 + } +}) +``` + +### 3.2 实际项目代码解析 + +在 `submit.vue:54-115`,`handleFileUpload` 函数中: + +```javascript +const handleFileUpload = async (items: UploaderFileListItem | UploaderFileListItem[]) => { + const files = Array.isArray(items) ? items : [items] + + const uploadPromises = files.map(async (item) => { + const file = item.file as File + + // 压缩文件 + let compressedFile = file + try { + compressedFile = await new Promise((resolve, reject) => { + new Compressor(file, { + quality: 0.8, // 压缩质量 0-1 之间 + maxWidth: 1280, // 最大宽度 + maxHeight: 1280, // 最大高度 + success(result) { + // 转换为 File 对象,保持原始文件名 + resolve(new File([result], file.name, { + type: 'image/jpeg', // 统一输出为 jpeg + lastModified: Date.now() + })) + }, + error(err) { + reject(err) + } + }) + }) + } catch (error) { + console.error('压缩失败:', error) + // 压缩失败时使用原文件 + } + + // 继续上传... + }) +} +``` + +**关键点:** +- 使用 Promise 包装 Compressor 的回调式 API +- 压缩失败时降级使用原文件 +- 转换结果为标准 File 对象,便于后续处理 + +## 4. 配置参数详解 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `quality` | number | 0.8 | 压缩质量,0-1 之间,值越小文件越小但质量越差 | +| `maxWidth` | number | Infinity | 最大宽度(像素) | +| `maxHeight` | number | Infinity | 最大高度(像素) | +| `minWidth` | number | 0 | 最小宽度 | +| `minHeight` | number | 0 | 最小高度 | +| `width` | number | - | 固定宽度(优先级高于 maxWidth) | +| `height` | number | - | 固定高度(优先级高于 maxHeight) | +| `resize` | string | 'cover' | 调整大小模式:'none'\|'contain'\|'cover'\|'crop' | +| `crop` | boolean | false | 是否裁剪(需配合 width/height) | +| `rotate` | number | 0 | 旋转角度(度) | +| `blur` | number | 0 | 模糊半径 | +| `grayscale` | boolean | false | 转为灰度图 | +| `input` | string | 'file' | 输入类型 | +| `output` | string | 'blob' | 输出类型:'blob'\|'base64'\|'datauristring' | + +## 5. 常见使用场景 + +### 5.1 批量图片压缩 + +```javascript +const compressFiles = async (files) => { + const promises = files.map(file => { + return new Promise((resolve, reject) => { + new Compressor(file, { + quality: 0.8, + maxWidth: 1280, + maxHeight: 1280, + success: resolve, + error: reject + }) + }) + }) + + return await Promise.all(promises) +} +``` + +### 5.2 限制文件大小 + +```javascript +const compressWithSizeLimit = (file, maxSizeMB = 2) => { + return new Promise((resolve, reject) => { + const maxSizeBytes = maxSizeMB * 1024 * 1024 + + new Compressor(file, { + quality: 0.6, // 降低质量确保文件更小 + maxWidth: 1024, + maxHeight: 1024, + success: (result) => { + if (result.size > maxSizeBytes) { + // 仍超出限制,进一步压缩 + new Compressor(file, { + quality: 0.4, + maxWidth: 800, + maxHeight: 800, + success: resolve, + error: reject + }) + } else { + resolve(result) + } + }, + error: reject + }) + }) +} +``` + +### 5.3 自定义输出格式 + +```javascript +// 输出为 base64 +new Compressor(file, { + quality: 0.8, + maxWidth: 1280, + maxHeight: 1280, + output: 'datauristring', + success: (result) => { + const base64String = result // 已经是 base64 字符串 + } +}) + +// 转换为 PNG +new Compressor(file, { + quality: 0.9, + maxWidth: 1280, + maxHeight: 1280, + success: (result) => { + new File([result], 'image.png', { type: 'image/png' }) + } +}) +``` + +## 6. 最佳实践 + +### 6.1 错误处理 + +```javascript +let compressedFile = originalFile +try { + compressedFile = await new Promise((resolve, reject) => { + new Compressor(file, options) + }) +} catch (error) { + console.error('压缩失败:', error) + // 压缩失败时使用原文件,避免阻塞上传流程 +} +``` + +### 6.2 文件类型检查 + +```javascript +const isImage = (file) => { + return /^image\//.test(file.type) +} + +if (!isImage(file)) { + throw new Error('仅支持图片文件') +} +``` + +### 6.3 压缩级别建议 + +- **高质量**(适合产品图、头像): + ```javascript + { + quality: 0.9, + maxWidth: 1920, + maxHeight: 1920 + } + ``` + +- **中等质量**(适合一般场景): + ```javascript + { + quality: 0.8, + maxWidth: 1280, + maxHeight: 1280 + } + ``` + +- **高质量压缩**(适合缩略图、证明材料): + ```javascript + { + quality: 0.6, + maxWidth: 800, + maxHeight: 800 + } + ``` + +### 6.4 进度提示(适用于 Vue/Vant) + +```javascript +item.status = 'uploading' +item.message = '压缩中...' + +try { + // 压缩 + item.message = '上传中...' + + // 上传 + item.status = 'done' + item.message = '上传成功' +} catch (error) { + item.status = 'failed' + item.message = '上传失败' +} +``` + +## 7. 性能优化 + +1. **避免重复压缩**:上传前检查文件是否已压缩 +2. **合理设置尺寸**:根据业务需求设置最大宽高 +3. **并发控制**:避免同时压缩过多文件 +4. **使用 Web Workers**:大量文件时考虑使用 Web Worker + +```javascript +// 并发控制示例 +const processFilesWithLimit = async (files, limit = 3) => { + const results = [] + const executing = [] + + for (const file of files) { + const promise = Promise.resolve().then(() => compressFile(file)) + results.push(promise) + + if (limit <= files.length) { + const executing_promise = promise.then(() => + executing.splice(executing.indexOf(executing_promise), 1) + ) + executing.push(executing_promise) + + if (executing.length >= limit) { + await Promise.race(executing) + } + } + } + + return Promise.all(results) +} +``` + +## 8. 注意事项 + +1. **只支持图片格式**:CompressorJS 仅处理图片文件(jpeg, png, gif 等) +2. **Canvas 依赖**:依赖浏览器 Canvas API,部分旧浏览器可能不支持 +3. **内存使用**:大文件压缩会消耗较多内存,建议控制文件大小 +4. **EXIF 方向**:压缩可能会丢失图片 EXIF 方向信息 +5. **文件类型转换**:默认输出 blob,转换为 File 时需显式指定类型 + +## 9. 完整示例 + +参考项目中的完整实现: +- 文件:`src/pages/approval/submit.vue:54-115` +- 结合 Vant Uploader 组件的批量图片上传和压缩 +- 集成错误处理和状态管理 +- 支持压缩失败时的降级策略 + +## 10. 常见问题 + +**Q: 压缩后文件变大?** +A: 原文件已非常小,或开启了失真优化。尝试降低 quality 或设置更小的 maxWidth/maxHeight。 + +**Q: 支持 GIF 动态图?** +A: 不支持,CompressorJS 仅处理静态图像。 + +**Q: 如何保持文件类型?** +A: 压缩时输出为 blob,然后在 File 构造函数中指定目标类型: +```javascript +const type = file.type.includes('png') ? 'image/png' : 'image/jpeg' +new File([result], file.name, { type }) +```