必须监听input的change事件,用FileReader或URL.createObjectURL读取文件并预览图片,注意accept属性、样式隔离、异常处理及iOS兼容性。

用 input type="file" 拦截原生上传行为,直接读取本地文件
Bootstrap 表单本身不提供图片预览能力,关键在接管 <input type="file" accept="image/*"> 的 change 事件,用 FileReader 读取选中的文件,避免触发表单提交或跳转。不要依赖任何后端接口做“预览”,那会延迟、失败且不必要。
常见错误是绑定 click 或 submit 事件去处理,结果文件根本没读进来;或者漏写 accept="image/*",导致用户误选 PDF 等非图文件,FileReader 读取后 result 为空字符串,img.src 赋值失败但无报错。
- 必须监听
input元素的change事件(不是click) - 检查
e.target.files[0]是否存在,再调用readAsDataURL() - 预览
<img>标签需提前在 HTML 中定义好,不要动态document.createElement后再塞进表单——容易被 Bootstrap 的.form-control样式撑变形
预览图要适配 Bootstrap 表单布局,别被 .form-control 撑歪
Bootstrap 默认给 <input class="form-control"> 设了固定高度和内边距,如果把 <img> 直接插在同级位置,它会被当成行内元素挤到输入框右侧,或因父容器 display: flex 导致尺寸失控。正确做法是用 <div class="mb-3"> 包裹整个上传区块,并为预览图单独加样式约束。
示例结构:
<div class="mb-3">
<label class="form-label">头像上传</label>
<input type="file" class="form-control" id="avatarInput" accept="image/*">
<div class="mt-2">
<img id="previewImg" src="" alt="预览" width="120" height="120" class="rounded border">
</div>
</div>
-
<img>必须设width和height(或用max-width: 100%+height: auto),否则加载前占位空白,加载后突然撑开页面 - 避免给
<img>加class="form-control"—— 它不是表单控件,加了反而套上边框/阴影/过渡动画 - 若需支持多图,
<img>改为<div id="previewContainer"></div>,内部用 JS 动态追加带class="rounded me-2"的缩略图
处理常见异常:空文件、非图像类型、读取失败
FileReader 的 onerror 不会抛出具体错误码,只设 reader.error 为 DOMException 对象,实际只能靠判断 result 是否为空或 file.type 是否匹配来兜底。用户点了取消、拖入文件夹、选中 .exe 文件,都会让预览失效但界面无反馈。
- 在
change回调开头加判断:if (!e.target.files || e.target.files.length === 0) return; - 检查
file.type.startsWith('image/'),比只看扩展名更可靠(用户可改后缀) -
reader.onload中,先清空img.src再赋新值,防止上次失败残留的data:URL 显示乱码 - 给预览区域加一句提示文字(如
<small class="text-muted">点击上方按钮选择图片</small>),上传成功后用 JS 隐藏它
移动端 Safari 下 FileReader 有时不触发 onload?换 URL.createObjectURL()
iOS 15+ 的 Safari 对 FileReader.readAsDataURL() 有缓存策略问题,尤其从相册选取照片后,onload 可能不执行。此时应降级使用 URL.createObjectURL(file),它返回一个临时 blob URL,兼容性更好,且无需等待 base64 编码完成。
注意两点:
-
createObjectURL()返回的 URL 必须在不用时手动释放,否则内存泄漏 —— 在下次change前调用URL.revokeObjectURL(currentSrc) - 不能对同一
file多次调用createObjectURL(),否则生成多个冗余 URL;每次新选文件前先 revoke 上一个 - 示例片段:
let currentPreviewUrl = null;<br>input.addEventListener('change', e => {<br> if (currentPreviewUrl) URL.revokeObjectURL(currentPreviewUrl);<br> const file = e.target.files[0];<br> if (file && file.type.startsWith('image/')) {<br> currentPreviewUrl = URL.createObjectURL(file);<br> previewImg.src = currentPreviewUrl;<br> }<br>});
revokeObjectURL。


















