HTML中img标签不自动纠正EXIF Orientation方向,因规范未要求且主流浏览器默认忽略;可靠方案是用piexifjs读取方向值,在canvas中按规则重绘并导出矫正后的图片数据。

移动端拍照上传的图片在 HTML 中显示歪了,不是前端渲染错了,是原始图像自带 EXIF Orientation 元数据没被处理。直接用 <img> 标签展示,浏览器不会自动按这个值旋转像素——只有部分 iOS 系统相册预览会“偷偷”转,但 Web 页面不会。
为什么 <img> 标签不自动纠正方向
HTML 规范没要求浏览器读取并应用 EXIF Orientation;Chrome、Firefox、Safari(桌面)默认忽略它;只有 Safari 移动端在某些场景下会做视觉补偿,但不可靠,且不改变实际像素布局。所以你看到“上传后变歪”,其实是图本身方向不对,只是原生相册帮你转了,而你的网页没转。
- EXIF
Orientation是拍摄时相机传感器朝向记录,不是 CSS 变换指令 -
<img src="xxx">加style="transform: rotate(90deg)"只是视觉覆盖,导出或截图仍是歪的 - 服务端如果直接存原始文件,后续所有下游使用(比如 PDF 生成、缩略图裁剪)都会继承错误方向
用 piexifjs 读取 Orientation 并在 Canvas 中重绘
真正可靠的纠正是:读取 EXIF 方向 → 创建合适尺寸的 <canvas> → 按规则把原始图像绘制进去 → 调用 canvas.toDataURL() 得到已矫正的新图片数据。
- 只对
file.type === "image/jpeg"处理(JPEG 才带可靠 Orientation;PNG/WebP 一般不带) -
Orientation值为6表示顺时针 90°(iOS 竖拍常见),需将宽高互换 + 旋转 -90° - 必须用
ctx.save()/ctx.restore()隔离变换,否则多次绘制会叠加错乱 - 不要用
ctx.rotate()直接转画布再 drawImage —— 容易裁边;应先ctx.translate()移动原点,再 rotate,再 drawImage
简化的关键逻辑示例:
立即学习“前端免费学习笔记(深入)”;
function fixOrientation(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async () => {
try {
const exifObj = piexif.load(reader.result);
const ori = exifObj?.["0th"]?.[piexif.ImageIFD.Orientation] || 1;
const img = new Image();
img.src = reader.result;
img.onload = () => {
let { width, height } = img;
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
<pre class='brush:php;toolbar:false;'> if ([6, 8].includes(ori)) [width, height] = [height, width];
canvas.width = width;
canvas.height = height;
ctx.save();
switch (ori) {
case 6: // 顺时针90° → 逆时针转90°
ctx.translate(width, 0);
ctx.rotate(0.5 * Math.PI);
break;
case 8: // 逆时针90° → 顺时针转90°
ctx.translate(0, height);
ctx.rotate(-0.5 * Math.PI);
break;
case 3:
ctx.translate(width, height);
ctx.rotate(Math.PI);
break;
}
ctx.drawImage(img, 0, 0);
ctx.restore();
resolve(canvas.toDataURL("image/jpeg", 0.9));
};
} catch (e) {
resolve(URL.createObjectURL(file)); // fallback
}
};
reader.readAsArrayBuffer(file);}); }
别用 exif-js 在 img onload 里硬套 CSS transform
这种写法看似快,但有三个硬伤:
- 仅影响当前 DOM 渲染,
canvas.toBlob()或fetch()上传的仍是原始歪图 -
transform依赖父容器尺寸和transform-origin,响应式布局下极易错位 - 安卓部分 WebView 和旧版 UC 浏览器不支持
transform作用于<img>的子元素(比如你转img.parentNode)
如果你只是临时预览、不涉及后续处理,那加个 class:img.exif-rotated-6 { transform: rotate(90deg) translateY(-50%); transform-origin: top left; } 可以应急,但别当正式方案。
上传前就矫正,而不是等后端或用户反馈
最省事的方式是在用户选完文件、还没调 fetch() 之前,就把 JPEG 文件转成已矫正的 Blob:
- 用
dataURLToBlob()把canvas.toDataURL()转回Blob,保持原始file.name和type - 避免在服务端重复解析 EXIF —— PHP 的
exif_read_data()在高并发下有性能瓶颈,且需要开启php_exif.dll - 注意:iOS 15+ Safari 对
FileReader.readAsArrayBuffer有内存限制,大图(>5MB)建议先用createImageBitmap解码降采样
纠正是个“一次做对”的事:前端做完,后面所有环节(压缩、裁剪、上传、PDF 导出)拿到的都是方向正确的像素数据。最容易被忽略的是——你以为 canvas 绘制完了就万事大吉,其实忘了把新生成的 data URL 再转成 Blob 传给后端,结果还是传了老图。



















