在 Uniapp 开发 H5 应用时,涉及到手机拍照上传的功能往往会遇到两个棘手的问题:
- 图片方向不对:不同厂商的手机(尤其是 iOS 和部分 Android)在拍照时,图片实际上是“横着”保存的,通过 EXIF 信息标记了方向。直接在
img标签显示可能正常,但一旦绘制到 Canvas 或上传到服务器,图片就会歪掉(通常是旋转了 90 度)。 - 图片体积过大:手机原图动辄 5MB+,直接上传消耗流量且速度慢,需要在前端进行压缩。
本文将分享一个基于 Exif.js 和 Canvas 的完整解决方案。
准备工作
我们需要引入两个核心工具库:
- Exif.js:用于读取图片的元数据(Metadata),核心是获取
Orientation(方向)标识。 - Mobile-detect(可选):用于辅助判断设备类型,处理特定机型的兼容性差异。
1. 引入 Exif.js
由于 exif.js 是一个较老的库,建议将其源码保存到项目中,例如 common/js/toolJs/exif.js。
(注:由于源码较长,此处不贴出完整库代码,请直接下载官方版本或使用 npm 安装)
2. 核心组件实现
我们将功能封装在一个 Vue 组件中。主要流程如下:
选择图片 -> 读取EXIF方向 -> Canvas重绘(旋转+压缩) -> 获取Base64 -> 上传。
HTML 结构
简单的界面,包含预览图、拍照按钮和上传按钮。
| |
核心 JS 逻辑
首先引入必要的库:
| |
关键步骤解析
- 获取图片并开启处理流程
使用
uni.chooseImage获取图片,然后调用detail方法进行处理。
| |
- 读取 EXIF Orientation 信息 这是修正方向的关键。我们需要封装一个 Promise 方法来读取图片的 Orientation 标签。
- 1: 正常
- 6: 顺时针旋转90度
- 8: 逆时针旋转90度
- 3: 旋转180度
| |
- Canvas 旋转与压缩
这是最复杂的环节。我们需要根据
Orientation的值,利用Canvas的rotate方法将图片“扳正”。同时,通过控制Canvas的尺寸来实现压缩。
注意:代码中加入了一个 getPlat() 判断,这是因为在实际测试中发现,部分 Android 环境下 Canvas 的旋转行为可能与 iOS 不一致,需要做特殊处理。
| |
- 旋转的具体实现 (rotateImg)
利用
Canvas上下文的rotate和drawImage。
| |
- 上传逻辑 最后,将获取到的 Base64 字符串(去除头部信息后)发送给后端。
| |
遇到的坑与总结
- Canvas 宽高混淆:当图片旋转 90 度时,Canvas 的 width 和 height 必须互换,否则图片会被裁剪或拉伸。
- Android 兼容性:代码中 this.getPlat() 的判断非常重要。部分 Android 手机的 WebView 或者浏览器在上传图片时会自动修正方向,如果我们再手动旋转一次,图片反而会歪掉。建议在真机上多测试几款主流机型。
- 内存溢出:对于超大分辨率的图片(如 4000x3000),Canvas 绘制可能会导致 iOS Webview 崩溃(OOM)。解决方案是在绘制前先大幅度压缩图片尺寸(如限制 maxWidth)。
通过以上方案,我们不仅解决了图片“歪脖子”的问题,还实现了前端压缩,大大减轻了服务器带宽压力。