核心是读取exif orientation并用canvas重绘纠偏,而非css旋转;需用exif-js解析、跨端适配路径、绕中心旋转绘制、统一promise链确保时序。

uni-app 图片上传前自动纠偏旋转,核心不是“转图”,而是「读取 EXIF Orientation 并按需修正」。不处理就传,iOS/安卓原图大概率歪 90° 或 180°;只用 CSS transform 预览,后端拿到的仍是歪的原始像素——必须在 canvas 层真正重绘。
怎么读取图片的 Orientation 值
不能靠 uni.getImageInfo 直接返回 orientation 字段(它在多数平台返回 undefined),得用 exif-js 解析原始图像元数据:
-
npm install exif-js --save,然后在页面或工具函数中 import - H5 端:用
new Image()加载tempFilePath(H5 是 blob URL,可直接 src 赋值);App 和小程序端需先调uni.downloadFile把临时路径转为本地可读路径(尤其 iOS 的 tempFilePath 生命周期极短) - 务必等
img.onload触发后再调EXIF.getData(img, ...),否则EXIF.getTag(this, 'Orientation')拿不到值 - 常见 orientation 值:1(正常)、6(顺时针 90°)、8(逆时针 90°)、3(180°);其他值极少,可统一按 1 处理
canvas 里怎么真正旋转并导出正确像素
别用 ctx.rotate() 直接绕左上角转——那只会把图切掉一半。要绕中心旋转,且必须重置坐标系,否则连续操作会累积错位:
- 先用
uni.getImageInfo拿原始宽高,避免压缩后比例失真 - 调
ctx.translate(centerX, centerY)把原点移到中心,再ctx.rotate(radians),最后ctx.translate(-centerX, -centerY) - 绘制时用
ctx.drawImage(img, -width/2, -height/2, width, height),确保整图居中填满 - 每次
ctx.draw(true, callback)必须加true强制清空缓冲,且 callback 里再调uni.canvasToTempFilePath,否则 iOS 返回空白图 - canvas 元素的
width和height属性必须设为整数(如 800×600),不能是百分比或 rem;style 宽高可另设缩放,但像素尺寸决定输出精度
不同平台怎么统一处理路径和渲染
同一套逻辑在 H5 / 小程序 / App 三端行为差异极大,路径不能硬传:
- H5:
tempFilePath是 blob URL,可直接赋给img.src,也能被 canvasdrawImage读取 - 微信小程序:
tempFilePath是本地临时路径,img.src和ctx.drawImage都能直接用,无需额外下载 - App(尤其 iOS):
tempFilePath可能几秒后失效,必须立刻uni.downloadFile存到plus.io.convertLocalFileSystemURL路径,再传给 canvas - 预览时若只用 CSS
transform: rotate(),只是视觉矫正,上传文件仍是歪的——纠偏必须落在 canvas 绘制这一步
为什么纠偏后上传还是歪?检查这三点
纠偏失败往往不是逻辑错,而是跨端细节漏了:
- 没对齐原始分辨率:比如原图 4032×3024,你 canvas 设成 400×300 再 drawImage,缩放 + 旋转双重失真,坐标全乱
- 忽略设备像素比(dpr):iPhone 上 canvas 实际缓存是 2x 分辨率,但你用 DOM 获取的裁剪框 left/top 是 1x 坐标,换算时没 ×2
- EXIF 信息被剥离:H5 端用
canvas.toDataURL()或压缩库时,exif 默认丢失;如果业务依赖拍摄时间、GPS 等,得用exif-js重写入(复杂度陡增,通常只纠 orientation)
最易被忽略的是:orientation 读取和 canvas 绘制之间没有强制等待,尤其在 App 端 downloadFile 是异步的,但很多人把它当同步链写,结果 canvas 画了个空 img 对象。务必把 orientation 获取、路径转换、canvas 初始化、绘制、保存串成 Promise 链,每步 reject 都有 fallback。











