Android Chrome:accept="image/*" 会丢 EXIF,不加 accept 就不会
📷 同一张照片、同一个页面、同一个 Chrome:
<input type="file" accept="image/*">选出来的文件没有 EXIF,把accept去掉后 EXIF 完好无损。区别不在前端代码,而在于 Chrome 拉起了不同的 Android 选图入口。
现象
<!-- ❌ 上传后 EXIF 丢失(至少 GPS 没了,常常是全部没了) -->
<input type="file" accept="image/*" />
<!-- ✅ 上传后 EXIF 完整保留 -->
<input type="file" />
两者选的是磁盘上同一个文件,但前者拿到的 File:
size比原图小(或完全不同)name可能变成image.jpg、1000012345.jpg这类系统生成的名字exifr.parse(file)返回undefined或缺失 GPS
为什么
accept 在 Android 上不只是个“过滤器”,它会改变 Chrome 发出的 Intent,从而改变是谁在给你文件。
| 写法 | Chrome 拉起的东西 | 文件来源 | EXIF |
|---|---|---|---|
accept="image/*" | 系统图片选择器 / Photo Picker / Google 相册 | MediaStore 媒体数据库返回的副本 | 被脱敏或重编码 |
无 accept | 通用文件选择器(Documents UI / 文件管理器) | 文件系统上的原始字节 | 完整保留 |
具体到两条路径:
路径一:图片选择器(加了 accept)
相册原图(EXIF 完整)
↓ MediaStore / Photo Picker / Google Photos Provider
系统生成一份“可分享副本”
• Android 10+ 自动删除 GPS(需 ACCESS_MEDIA_LOCATION 才给原图,Chrome 不申请)
• 云端照片/HEIC 还会被下载、缩图、转码成 JPEG
↓
Chrome 拿到的就是这份副本
关键在于:脱敏发生在文件交给浏览器之前。到达 JS 的 File 本身就不带 EXIF,前端怎么读都读不出来。
路径二:文件选择器(不加 accept)
不声明类型时,Chrome 拉起的是通用文件浏览器。它把图片当成普通文件直接读取字节流,不走媒体库的“分享副本”逻辑,因此 EXIF(包括 GPS)原样保留。
验证方法
把两个 input 放在同一个页面上,选同一张照片对比:
<input id="a" type="file" accept="image/*" />
<input id="b" type="file" />
<script type="module">
import exifr from 'https://cdn.jsdelivr.net/npm/exifr/dist/full.esm.mjs'
for (const id of ['a', 'b']) {
document.getElementById(id).onchange = async (e) => {
const file = e.target.files[0]
const exif = await exifr.parse(file, true)
console.log(id, {
name: file.name,
size: file.size,
tags: exif ? Object.keys(exif).length : 0,
gps: await exifr.gps(file),
})
}
}
</script>
典型输出:a 的 tags 为 0 或缺 GPS,b 标签齐全,且 b.size 等于原图大小。
解决方案
去掉 accept****。
<input type="file" />
代价是用户能选中非图片文件,所以把类型校验搬到 JS 里:
input.addEventListener('change', () => {
const file = input.files[0]
if (!file) return
// 不依赖 accept,自己校验(建议同时用魔数字节而不只看 MIME)
if (!/^image\//.test(file.type)) {
alert('请选择图片文件')
input.value = ''
return
}
upload(file) // 直传原始 File,不要过 canvas
})
几个配套注意点:
- 不要用
accept=".jpg,.jpeg,.png"代替。只要声明了图片类型,部分机型依旧会路由到图片选择器,需实测。 - 删掉
capture,它会直接调起相机,同样拿不到完整元数据。 - 拿到原图后不要再用 canvas 压缩,
canvas.toBlob()会把刚保住的 EXIF 全部丢掉。 - 即使不加 accept,如果用户从 Google 相册/云盘入口选图,仍可能拿到副本。引导用户从本地文件夹(DCIM/Camera)选择最稳。
一句话总结
安卓上 accept="image/*" 等于告诉系统“给我一张图片”,系统便给你一份去隐私、可能转码过的副本;不写 accept 则是“给我这个文件”,拿到的就是原始字节,EXIF 自然还在。