最近在处理移动端选择图片实时预览并上传时遇到一个问题:上传前图片预览正常,但上传到服务器上的图片展示到页面上时,有时会出现图片翻转的问题,一般是翻转 90 度。后经一翻研究找到了问题所在,特在此记录一下。

问题描述

接上面提到的问题,经过一些测试,发现:如果选取的图片是在横屏状态下拍摄的,则上传后不会出现图片翻转的问题;反之,如果是在竖屏状态下拍摄的,上传后就会出现图片翻转的问题。

问题分析

首先,图片在本地预览时正常,而且前端最后提交到后端的数据是用 FormData 来封装处理的,用 FormData 可以保证提交的数据和通常的表单提交没有什么区别,对后端来说是透明的,后端的处理逻辑不用修改就可以处理带文件的 Ajax 提交。因此,我猜想问题可能出在了后端。

但是后端也没有做什么特殊的处理,怎么展示的时候图片就翻转了呢?

OK,前面提到过这个问题和手机的拍照模式有关,竖屏下拍的照片会重现这个问题,横屏下的则不会。难道这两种模式下拍摄的照片不一样?照片中保存的数据不一样?,这不由的使我想到了 Exif 这个名词,虽然我对它不是十分了解,但是印象中它是用来保存照片的一些元数据信息的,也许和它有关。

简单的一搜索发现:Exif 的全称是 Exchangeable image file format,的确是用于保存照片的一些元数据信息,如照片的拍摄设备,拍摄时间,是否使用闪光灯等。其中有一项就是 Orientation (rotation),很明显应该就是它了。这里有一篇关于它的详细的介绍,此处不展开细讲。简单来说就是它有 8 个值,用来表示照片拍摄时相机旋转的状态,而且我们可以通过编程的方式读取它。我们平常用电脑来查看手机拍摄的照片时也会偶尔出现图片翻转的情况,其原因也是照片查看软件没有根据这个值将照片自动翻转。

经过搜索,在 SO 上找到了相关的问题。其思路就是读取照片的 Exif 信息,判断 Orientation 的值,然后将图片进行相应的旋转。问题已确定,后端同学很快就将这个问题给修复了,并且添加到了他们的公共模块中。

你以为问题就这样解决了,其时并没有。

后续

过了几天,公司购置了几台全新的测试机,测试同学将系统在一台三星的机子上一测,又发现新问题了:选择完图片进行本地预览时,发现图片翻转了!但上传后再展示又是正常的。WTF!

这次没得说了,问题肯定出在了前端预览上。但是之前在安卓和 iOS 的设备上测试都是正常的呀,难道和机型有关?问了一下测试,得知这台三星用的是 Android 5.0 的系统,好先进有木有!这难道是 5.0 中引入的一个问题?别想这些了,反正我们也不可能在系统层面去修复这个问题,还是想想如何去兼容 5.0 吧。

当时想到了两种方法,一种是选择图片后先上传到服务器,再展示上传的图片进行预览。这种方法的优点是兼容性好,前端处理相对简单;缺点是后端要提供图片上传的接口,并且如果用户在保存之前更换图片并不会删除之前上传的图片。移动端的微博配图就是用的这种方法。

另一种方法是在本地进行图片翻转。我们可以使用 FileReader API 来读取图片,之后像后端那样分析 Exif 信息,旋转图片。这种做法的优点是,预览操作完全在本地完成,不会产生不必要的文件上传。缺点是 FileReader 在 Android 4.1 及以上的机型才可以使用,加上前端处理文件数据可能稍显复杂,另外性能也是个问题。

但是最终我们选择了第二种方案。原因是我们之前的照片预览就是用的 FileReader API,不需要考虑低版本安卓的兼容性,这和项目的实际情况有关。另一个原因是我们认为也许有前人已经遇到过这种问题,也许已经有了比较好的处理这个问题的功能模块。后经搜索,找到了一个 fix-orientation 模块,可以通过 npm i fix-orientation --save 使用,具体的用法可以参考项目的文档。后来,扫了一眼它的代码,发现它是用 canvas 来实现图片的旋转,并不会对正常的图片进操作,性能问题不大,iOS 上秒开,安卓设备上稍长,可以添加 loading 效果缓解一下。

至此,问题完美解决。

另外,像 fix-orientation 这样小而美的模块有很多,大家平时可以多关注一下。