优先选 url.createobjecturl(),它同步生成内存中 blob 的临时 url,速度快、内存低,适合纯预览;而 readasdataurl() 异步转 base64,大文件易卡顿且有长度限制。

用 URL.createObjectURL() 还是 FileReader.readAsDataURL()
优先选 URL.createObjectURL(),它不读取文件内容,只生成指向内存中文件对象的临时 URL,速度快、内存占用低,适合纯预览场景。而 FileReader.readAsDataURL() 会把整个文件转成 base64 字符串,大图(比如 8MB 的 PNG)容易卡顿,甚至触发某些后端对字段长度的限制。
但要注意:URL.createObjectURL() 生成的 URL 不可跨页面、不可持久化,刷新即失效;如果后续要上传原图或做 canvas 处理(如旋转、压缩),FileReader 更可控。
- 只预览 → 用
URL.createObjectURL() - 要读尺寸、改 EXIF、裁剪、或需 base64 提交 → 用
FileReader.readAsDataURL() - 多图预览时,
URL.createObjectURL()搭配URL.revokeObjectURL()是必须的,否则内存泄漏肉眼可见
input 该监听 input 还是 change 事件
监听 input 事件。用户在文件选择器里点“取消”或拖入文件后松手,input 会立刻触发;而 change 在部分浏览器(尤其 Safari)中可能延迟到 input 失焦才触发,导致预览滞后或不响应。
另外,input.files 是只读的实时快照,每次选新文件都会更新,所以不需要缓存或手动清空——但你得检查 input.files.length > 0,否则用户取消选择时 input.files[0] 是 undefined,直接取会报错。
- 别用
change做预览主逻辑,尤其涉及拖放(drop)时更不准 - 监听
input后,第一件事就是判断if (!input.files.length) return - 若同时支持拖放,需额外监听
drop和dragover,并阻止默认行为
为什么图片预览空白?常见三处硬伤
最常踩的坑不是语法错,而是逻辑断点:
-
img.src = file——file是File对象,不是 URL,浏览器无法识别,必须过URL.createObjectURL(file) - 没调
URL.revokeObjectURL(img.src)就换新图 —— 旧 URL 还占着内存,新图可能加载失败或显示异常 - 选了非图片文件(比如 .pdf 或 .txt),但没校验
file.type.match('image.*'),结果传了个空字符串给img.src,自然空白
补充一点:有些老版 Safari 对 image/webp 不支持,预览时显示为空白,但控制台无报错,建议加 fallback 提示或服务端兜底转格式。
多图预览怎么动态管理 DOM 和内存
HTML 加 multiple 属性只是第一步,关键在 JS 怎么清理和重建预览容器。不能简单 innerHTML = '',那样会漏掉已创建的 object URL 引用,造成内存泄漏。
推荐做法:遍历现有 <img> 元素,从其 src 提取 URL 并 URL.revokeObjectURL(),再移除节点;然后为每个新 File 创建新 <img> 并绑定 load 和 error 回调(用于失败降级)。
- 每张预览图对应一个独立的
URL.createObjectURL()调用,不要复用 - 移除某张预览图时,必须同步
URL.revokeObjectURL()对应的 URL - 预览容器用
<div id="preview-container">,别用 <code><img>单标签硬编码,否则多图无法扩展 - 移动端注意
object-fit: contain+max-width: 100%,否则高宽比失真
复杂点在于:用户反复增删文件时,input.files 不反映最终上传列表(它是只读快照),真正要提交的文件必须维护一个独立数组,靠 input 事件 + 手动增删来同步。











