拖拽区域必须设draggable="false"和tabindex="0":前者防止内部文本被意外拖走,后者使其可键盘聚焦并响应空格/回车触发文件选择;dragover中必须调用preventdefault(),否则drop事件不会触发。

拖拽区域必须加 draggable="false" 和 tabindex="0"
不加这两个属性,区域无法被键盘聚焦,也无法阻止内部文本被意外拖走。浏览器默认把普通 <div> 当作不可拖拽目标,<code>draggable="false" 是显式声明“我不参与拖拽源,但我可以接收”,否则鼠标划过文字时会触发系统级文本拖拽,干扰 drop 行为。tabindex="0" 让它能被 Tab 键选中,空格/回车可触发文件选择(无障碍和键盘用户刚需)。
- 别用
<button></button>或<label></label>包裹整个区域——它们自带点击逻辑,容易和drop事件冲突 - 禁止设
contenteditable="true",Safari 下会吞掉drop事件 -
role="region"和aria-label="上传文件区域"必须带上,否则屏幕阅读器无法识别功能意图
dragover 不调 preventDefault() 就等于没写 drop 监听
drop 事件根本不会触发,除非 dragover 中调用了 e.preventDefault()。浏览器默认行为是打开文件或导航跳转,不阻止它,松手瞬间就跳走了。这不是可选项,是硬性前提。
- 只绑
drop但漏掉dragover:常见于复制粘贴教程,结果拖进去没反应 - 在
dragover里做 heavy 操作(比如读取FileList或渲染预览):它每 30–50ms 触发一次,UI 会卡顿 - 移动端 Safari 完全不支持
dragenter/dragover,必须保留<input type="file">并用click()触发作为降级
dataTransfer.files 是只读类数组,得转成真数组才能用 map/filter
event.dataTransfer.files 返回的是 FileList,不是 Array,不能直接调 .map() 或 .forEach()。强行用会报 TypeError: files.map is not a function。
- 正确转换方式:
Array.from(e.dataTransfer.files)或[...e.dataTransfer.files] - 过滤大文件建议用
file.size(同步获取),别等FileReader异步读完再判断 - 校验类型别只信
file.type,后缀可伪造,前端仅作快速拦截,服务端必须二次校验 MIME
FormData.append() 的字段名必须和服务端接收字段严格一致
前端写 formData.append('upload', file),后端框架(Express 的 req.files.upload、Django 的 request.FILES['upload'])就必须按 upload 这个 key 解析。名字错一个字母,req.files 就是空对象或 undefined。
- 多文件上传时,用同一字段名多次
append(如append('files', f1)、append('files', f2)),后端需按数组方式接收 - 如果要传额外参数(如
category),也得用append('category', 'image'),不能塞进 JSON 字符串里 - 上传前检查
files.length > 0,避免空FormData发出去导致后端 400
dragover 的 preventDefault() 不只是“让 drop 生效”,它还决定了浏览器是否允许该区域成为合法投放目标——这个判断在事件冒泡阶段就完成了,后续任何 JS 逻辑都救不回来。











