1. 当uni.compressImage在H5端失效时该怎么办
遇到uni.compressImage在H5端报错"compressImage is not yet implemented"时,很多开发者第一反应是懵的。这个API在小程序和App端用得好好的,怎么一到H5就罢工了?其实这是因为uni-app的某些API在不同平台上的实现程度不同。H5平台由于浏览器环境的限制,部分原生API确实无法直接使用。
这时候我们需要理解几个关键点:
- 浏览器环境本身没有提供原生的图片压缩API
- uni.compressImage在小程序和App端的实现是调用了原生能力
- H5端需要我们自己实现压缩逻辑
我遇到过不少项目因为这个"小问题"导致整个图片上传功能瘫痪。最夸张的一次是客户上传了10MB的图片直接把服务器搞崩了,紧急修复时才发现H5端根本没做压缩。所以这个问题看似小,实则影响很大。
2. Canvas手动压缩方案详解
2.1 基础实现原理
Canvas压缩图片的核心思路很简单:把图片绘制到Canvas上,然后通过调整Canvas的大小和质量参数输出压缩后的图片。这个过程可以分为几个步骤:
- 使用FileReader读取原始图片文件
- 创建Image对象加载图片
- 计算合适的压缩尺寸
- 创建Canvas并绘制图片
- 使用toDataURL或toBlob方法输出压缩结果
// 基本压缩函数框架 function compressImage(file) { return new Promise((resolve) => { const reader = new FileReader() reader.readAsDataURL(file) reader.onload = (e) => { const img = new Image() img.src = e.target.result img.onload = () => { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') // 设置Canvas尺寸 canvas.width = img.width canvas.height = img.height // 绘制图片 ctx.drawImage(img, 0, 0, img.width, img.height) // 输出压缩结果 canvas.toBlob((blob) => { resolve(new File([blob], file.name, {type: file.type})) }, file.type, 0.7) // 0.7是质量参数 } } }) }2.2 优化压缩策略
基础实现虽然能用,但实际项目中我们需要考虑更多细节。比如:
- 尺寸压缩:大图应该先缩小尺寸再压缩质量
- 质量分级:根据原始文件大小采用不同的压缩比例
- 类型判断:不同图片格式(jpg/png)需要不同处理
我优化后的方案是这样的:
function compressImage(file, maxWidth = 800, quality = 0.7) { return new Promise((resolve) => { const fileSize = file.size / 1024 / 1024 // MB const reader = new FileReader() reader.readAsDataURL(file) reader.onload = (e) => { const img = new Image() img.src = e.target.result img.onload = () => { let width = img.width let height = img.height // 尺寸压缩 if (width > maxWidth || height > maxWidth) { const ratio = width > height ? maxWidth / width : maxWidth / height width *= ratio height *= ratio } // 质量分级 let finalQuality = quality if (fileSize > 2) finalQuality = 0.5 else if (fileSize > 1) finalQuality = 0.6 const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') canvas.width = width canvas.height = height ctx.drawImage(img, 0, 0, width, height) // PNG图片保持质量 if (file.type === 'image/png') { finalQuality = 0.9 } canvas.toBlob((blob) => { resolve(new File([blob], file.name, {type: file.type})) }, file.type, finalQuality) } } }) }2.3 实际应用中的坑点
在实际项目中应用Canvas压缩时,我踩过不少坑:
- iOS设备上的方向问题:有些手机拍摄的照片在Canvas中会旋转。需要处理EXIF方向信息。
- 透明背景问题:JPG压缩透明背景会变黑,需要特殊处理。
- 内存泄漏:大量压缩操作可能导致内存问题,需要及时清理对象。
针对方向问题,我通常会引入exif-js库来处理:
import EXIF from 'exif-js' function getOrientation(file) { return new Promise((resolve) => { EXIF.getData(file, function() { const orientation = EXIF.getTag(this, 'Orientation') || 1 resolve(orientation) }) }) } // 然后在img.onload中根据orientation调整Canvas绘制方式3. 使用compressorjs第三方库的方案
3.1 为什么选择compressorjs
当项目时间紧或者需要更稳定的压缩方案时,我会选择compressorjs这个专门做图片压缩的库。它有这些优势:
- 开箱即用,API简单
- 自动处理了EXIF方向等常见问题
- 提供了更多压缩选项和钩子函数
- 社区活跃,问题容易解决
安装很简单:
npm install compressorjs --save3.2 基础使用示例
基本使用方式非常直观:
import Compressor from 'compressorjs' function compressWithCompressor(file) { return new Promise((resolve) => { new Compressor(file, { quality: 0.6, maxWidth: 800, maxHeight: 800, success(result) { resolve(new File([result], file.name, {type: result.type})) }, error(err) { console.error(err) resolve(file) // 失败时返回原文件 } }) }) }3.3 高级配置技巧
compressorjs提供了丰富的配置选项,可以根据项目需求调整:
new Compressor(file, { quality: 0.6, maxWidth: 1200, maxHeight: 1200, convertSize: 1024 * 1024, // 超过1MB的PNG转成JPG mimeType: 'auto', // 自动选择最佳格式 strict: true, // 严格模式确保输出符合要求 checkOrientation: true, // 自动处理方向 retainExif: false, // 不保留EXIF信息节省体积 beforeDraw(context, canvas) { // 绘制前钩子,可以做一些自定义处理 }, drew(context, canvas) { // 绘制后钩子 }, })4. 两种方案的对比与选择建议
4.1 性能对比
在实际项目中,我对两种方案做了详细测试:
| 指标 | Canvas方案 | compressorjs |
|---|---|---|
| 压缩速度 | 较快 | 稍慢 |
| 压缩率 | 可调 | 更优 |
| 内存占用 | 较低 | 较高 |
| 兼容性 | 好 | 更好 |
| 功能完整性 | 基础 | 全面 |
4.2 适用场景建议
根据我的经验,我会这样选择:
- 简单项目/快速实现:使用Canvas方案,代码量少,依赖少
- 复杂需求/生产环境:使用compressorjs,稳定性更好
- 特殊格式处理:compressorjs对WebP等新格式支持更好
- 低端设备兼容:Canvas方案内存占用更低
4.3 优雅降级策略
在实际项目中,我通常会实现一个自动降级的方案:
async function smartCompress(file) { // 小文件不压缩 if (file.size < 1024 * 1024) return file try { // 优先尝试compressorjs return await compressWithCompressor(file) } catch (e) { console.warn('compressorjs失败,降级到Canvas方案') // 失败后降级到Canvas方案 return await compressWithCanvas(file) } }这种策略既保证了最佳体验,又有兜底方案。我在多个项目中验证过这种方案的可靠性,特别是在一些特殊浏览器环境下,这种分层处理的方式能显著提高成功率。