必须用 FileReader 读取 File 为 data URL,再通过 Image 加载并监听 onload 获取 naturalWidth/naturalHeight 计算原始宽高比;比对时需设容差(如±0.02),避免浮点精度问题导致误判。

上传前用 FileReader + Canvas 检查原始宽高比
浏览器拿到 File 对象后,不能直接读取图片元数据(如 EXIF 中的宽高),但可以用 FileReader 读取为 data URL,再用 Image 加载解析真实尺寸。这是唯一能拿到「原始像素宽高」的方式,绕过所有压缩/导出导致的元数据丢失问题。
常见错误是只读取 file.size 或依赖 input.files[0].type,这些跟比例无关;也别信某些库返回的“宽高”字段——很多只是文件名里猜的或 header 里误判的。
- 必须等
img.onload触发后再取img.naturalWidth和img.naturalHeight - 避免在
img.onerror里静默吞掉失败:损坏图、跨域图、非图片 MIME 都会进这里 - 比例计算用
img.naturalWidth / img.naturalHeight,别四舍五入,保留小数用于后续比对(比如 1.777… vs 1.78)
限制 4:3 或 16:9 等固定比例时怎么比对
不要用 === 直接比较浮点结果,原始图哪怕标称 16:9,实际可能是 3840×2160(精确)或 4000×2248(近似)。应设容差,例如 ±0.02。
示例逻辑:
立即学习“前端免费学习笔记(深入)”;
const ratio = img.naturalWidth / img.naturalHeight;
const targetRatio = 16 / 9; // 1.777...
if (Math.abs(ratio - targetRatio) > 0.02) {
alert('图片宽高比不符合 16:9 要求');
return;
}- 16:9 容差 0.02 覆盖约 3830×2150 到 3850×2168 的范围
- 4:3 场景建议用
ratio > 1.31 && ratio ,比固定减法更稳 - 若需支持多比例(如“4:3 或 16:9”),用数组
[4/3, 16/9]循环比对,别写一堆else if
用户选错图后怎么友好提示并阻止提交
光 alert 不够:移动端弹窗易被拦截,且无法聚焦到对应 <input type="file"> 元素。应该做三件事:视觉标记 + 禁用提交按钮 + 清空已选文件引用。
- 给 input 加
class="error"并配红边框样式,比文字提示更早被注意到 - 提交按钮设
disabled,同时input.files = null(注意不是.value = '',后者在部分 Safari 版本无效) - 如果用了封装组件(如 Vue 的
v-model或 React 的受控 input),需同步重置 state,否则下次 change 事件不触发
为什么不能靠后端校验就省掉前端检查
可以,但体验极差:用户填完所有表单、写好标题描述、等上传完成,最后才被告知“比例不对”,此时页面可能已跳转或状态丢失。尤其移动端,重新选图要两次点击(打开相册 → 找图 → 点击)。
更关键的是,Canvas 检查本身很快,drawImage 不绘制到屏幕,纯内存操作,10MB 图片也通常在 50ms 内完成。真正耗时的是 FileReader.readAsDataURL,但它不可避免——你总得让图片显示预览或生成缩略图。
容易被忽略的一点:某些 CMS 或微信转发图会抹除 naturalWidth/naturalHeight,表现为 0。这时需 fallback 到 canvas 绘制后读取 canvas.width/canvas.height,虽然稍慢,但能兜底。



















