是浏览器唯一合法文件上传入口,禁止js赋值、模拟点击或动态创建后设value;必须用户真实交互触发,正确做法是label包裹或opacity:0覆盖;name属性必设,multiple支持多选,上传须用formdata且不可手动设content-type。

用 input type="file 是唯一合法入口
浏览器不支持 JS 直接赋值路径、不能模拟点击触发选择框、也不允许通过 document.createElement('input') 后设 value 绕过用户交互——这是硬性安全限制,不是兼容性问题。你必须让用户真实点击或拖拽触发原生文件选择器。
常见错误:写了个按钮,onclick 里调 input.click(),但 input 是 display: none 或未插入 DOM,部分浏览器会静默拒绝。正确做法是用 label 包裹 input,或用 opacity: 0 + position: absolute 覆盖在自定义按钮上。
-
name属性必须设置,否则后端收不到字段(比如 Express 的multer会忽略无 name 的文件) -
accept只影响文件选择器界面过滤,用户切到“所有文件”仍能选任意类型,真实校验得靠 JS 读file.type或后缀名 - 移动端需注意:iOS Safari 对
accept="image/*"支持较弱,建议补上.jpg,.png等具体后缀
多文件上传必须加 multiple 属性
仅靠 Ctrl/Cmd 多选不生效,input 元素本身必须带 multiple 才能返回多个文件。此时 input.files 是 FileList,不是数组,不能直接用 map 或 forEach。
要转成数组才能操作:Array.from(input.files) 或 [...input.files]。遍历时注意检查 file.size,前端拦截超限文件(比如 >100MB),避免白传。
- 服务端字段名若需统一接收(如 PHP 的
$_FILES['files']),可设name="files[]" - 单个
formData.append('files', file)会覆盖前一个;要传多个,必须循环调用,且字段名一致(后端按同名合并为数组) - 不要用
for...in遍历FileList,它有 length 属性但不是对象字面量,容易出错
拖拽上传要防浏览器默认行为
监听 drop 事件时,若不调 e.preventDefault(),Chrome/Firefox 会直接打开文件(PDF 就预览,图片就跳新页),根本进不到你的上传逻辑。
同时要监听 dragover 并阻止默认行为,否则拖入时鼠标显示“禁止”图标,用户以为不支持拖拽。
-
event.dataTransfer.files和input.files类型一致,可同样用Array.from()处理 - 拖拽支持文件夹(
webkitdirectory),但返回的是扁平FileList,无目录结构,且 Firefox/Edge 不支持 - 拖拽区域建议设最小高度(如
min-height: 80px),避免因内容少导致热区过小
上传前必做三件事:校验、构造、发请求
拿到 File 对象后不能直接 fetch(url, { body: file }) —— 这会当纯文本发,后端收不到二进制流。必须走 FormData。
关键细节:不要手动设 Content-Type header。一旦写了 headers: { 'Content-Type': 'multipart/form-data' },浏览器就无法注入 boundary,后端解析器(如 multer)直接丢弃整个 body,返回 400 且不报错。
-
formData.append('attachment', file)第二个参数必须是File或Blob,传字符串会被当普通字段 - 可同时 append 文本字段:
formData.append('doc_id', '123'),后端在同个请求里就能拿到文件和元数据 - 大文件建议加
AbortController,否则用户点了取消,fetch 请求仍在后台跑,浪费带宽
FileList 处理链,真正容易被忽略的是:服务端是否真的配置了 multipart 解析中间件(Express 忘装 multer、Flask 误读 request.form 而非 request.files)、Nginx 是否卡在 client_max_body_size、以及字段名大小写是否和服务端完全一致。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











