iOS11下如何在上传前用JavaScript检测HEIC图片?
这个iOS11的HEIC转JPG坑确实让人头疼——系统偷偷改了文件的扩展名和MIME类型,导致常规检测完全失效,还触发compress.js崩溃。我来给你个靠谱的解决方案,从文件的二进制签名入手检测,绕过iOS的伪装:
问题根源
iOS11在用户选择HEIC/HEIF图片后,会自动将文件重命名为.jpg,并把MIME类型设为image/jpg,但这个转换后的文件其实是不兼容compress.js的“伪JPG”,导致压缩时崩溃。常规的MIME检测、accept属性限制都因为iOS的静默修改而失效。
解决方案:检测文件二进制签名
不管iOS怎么改扩展名和MIME类型,HEIC文件的二进制文件头签名是不会变的。我们可以读取文件的前几个字节,对比HEIC的专属签名来识别它。
1. 定义HEIC的文件签名
HEIC/HEIF文件的开头8字节通常是以下几种签名之一:
const HEIC_SIGNATURES = [ [0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63], // ftypheic [0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x78], // ftypheix [0x66, 0x74, 0x79, 0x70, 0x6D, 0x69, 0x66, 0x31] // ftypmif1(HEIF变种) ];
2. 编写检测函数
用FileReader读取文件的前8字节,对比签名:
function isHEICFile(file) { return new Promise((resolve) => { const reader = new FileReader(); // 只读取前8字节,性能高效 reader.readAsArrayBuffer(file.slice(0, 8)); reader.onload = (e) => { const uint8Array = new Uint8Array(e.target.result); let isHEIC = false; // 逐一对比签名 HEIC_SIGNATURES.forEach(signature => { let match = true; for (let i = 0; i < signature.length; i++) { if (uint8Array[i] !== signature[i]) { match = false; break; } } if (match) isHEIC = true; }); resolve(isHEIC); }; // 读取失败时默认返回非HEIC reader.onerror = () => resolve(false); }); }
3. 在文件选择事件中使用检测
修改你的原有代码,先检测HEIC,再处理常规图片:
$(document).on('change', '[id=uploading_file]', async (evt) => { 'use strict'; const files = [...evt.target.files]; const uploadingFile = files[0]; // 优先检测HEIC(即使被iOS伪装成jpg) const isHEIC = await isHEICFile(uploadingFile); if (isHEIC) { alert('HEIC格式图片暂不支持上传,请选择JPG/PNG等格式的图片'); evt.target.value = ''; // 清空文件选择,避免重复提交 return; } // 常规图片格式验证 const ValidImageTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif", "image/bmp"]; if (ValidImageTypes.includes(uploadingFile.type)) { // 这里执行你的compress.js压缩和上传逻辑 console.log('开始处理有效图片'); } else { alert('请选择支持的图片格式'); evt.target.value = ''; } });
额外说明
accept属性在iOS文件选择器中不可靠,系统经常会忽略该限制,所以必须配合二进制检测。- 只读取文件的前8字节,不会影响性能,也不会读取整个大文件。
内容的提问来源于stack exchange,提问作者S.Basnagoda




