不能依赖 input[type="file"].files 的顺序还原用户真实选择意图,因其在不同浏览器和操作方式下排序不可靠;必须通过 dragover 和 drop 事件结合鼠标坐标计算插入位置,并传递 order[] 字段确保前后端顺序一致。

不能靠 input[type="file"].files 的顺序来还原用户拖入或点击选择时的真实意图——这个数组在 Windows 多次点选、Safari 拖入、甚至 Chrome 117 之前都不可信。必须绕过它,用拖拽事件自己记序。
为什么不能直接用 event.target.files 排序
浏览器对 input[type="file"] 的 files 属性排序策略不统一:
• Windows 上 Ctrl+多选常按文件名升序重排
• Safari 偶尔按最后修改时间
• 即使用户从资源管理器拖入三张图 A/B/C,files[0] 也可能是 C
• files 是只读的 FileList,无法手动调整顺序,也无法插入/删除中间项
拖拽排序必须监听 dragover 和 drop 两个事件
仅绑定 drop 不够,dragover 必须调用 e.preventDefault(),否则 drop 根本不会触发——松手后页面可能直接跳转到图片地址或弹下载。
- 给上传容器(比如一个
<div id="drop-area">)同时监听 <code>dragover和drop -
dragover里只做两件事:e.preventDefault()+ 设置e.dataTransfer.dropEffect = "copy" -
drop里立刻取e.dataTransfer.files,并用鼠标坐标定位插入位置 - 别在
dragenter/dragleave里操作 DOM,它们只适合加/去 hover 类 - 遍历当前已渲染的
<img>或占位<div> 元素,调用 <code>el.getBoundingClientRect() - 对比
e.clientY和每个元素的top + height / 2,找到最靠近鼠标的那个索引 - 把新拖入的
File对象用splice(insertIndex, 0, file)插入本地数组 - 重新渲染整个列表(不要 append,避免 DOM 错位)
- 上传时,给每个
FormData.append("files", file, file.name)加上序号字段,比如FormData.append("order[]", insertIndex) - 真要支持移动端排序,得放弃原生 API,改用
touchstart/touchmove/touchend自行模拟,或引入SortableJS - Safari 对
URL.createObjectURL()回收更激进,预览图插入后记得存引用,删除时必须调URL.revokeObjectURL() - 如果 UI 允许,优先引导移动端用户用「点击选择 → 手动拖拽缩略图」,而不是直接拖文件进页面
怎么用鼠标坐标确定新文件该插在哪
核心不是“拖进来就追加”,而是“拖到哪,就插到对应缩略图前面”。需要实时计算鼠标相对于已有预览图的位置。
移动端和 Safari 的现实约束
原生 drag/drop 在 iOS/Safari 上基本不可用——触摸事件不触发 dragstart,dataTransfer 为空,drop 也不触发。这不是 bug,是规范限制。
最易被忽略的一点:顺序一致性必须贯穿前后端。前端用拖拽确定了插入位置,后端接收时不能只按 $_FILES 或 multipart body 的自然顺序存,得依赖传来的 order[] 字段重排——否则用户拖半天,列表还是按文件名排。











