必须阻止dragover默认行为才能触发drop事件,再读取e.dataTransfer.files获取文件列表,最后用FormData发送请求;accept属性无效,校验须在drop回调中手动完成。
直接用 dragover + drop 事件就能实现基础拖放,但真正能上传文件的完整链路,必须处理好三件事:阻止默认行为、读取 DataTransfer 对象、用 FormData 发送请求。漏掉任意一步,拖进去就啥也不发生。
为什么拖进页面没反应?preventDefault() 必须加在 dragover
浏览器对 dragover 事件有默认拦截策略——不阻止它,就根本触发不了 drop。这不是可选项,是硬性前提。
dragenter 和 dragover 都要监听,但只有 dragover 必须调用 e.preventDefault()
dragenter 没用;只阻止 drop 更没用(它压根不会触发)contenteditable 或表单控件,可能还要额外处理 dragleave 防止样式错乱const dropZone = document.getElementById('drop-area');
dropZone.addEventListener('dragover', (e) => {
e.preventDefault(); // 关键!没有这行,下面的 drop 永远不会来
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
const files = e.dataTransfer.files; // 这里才能拿到文件列表
});e.dataTransfer.files 和 e.dataTransfer.items 怎么选?两者都能取文件,但行为差异明显:files 是 FileList(只含本地文件),items 是 DataTransferItemList(支持文件、文本、URL 等多种类型,且可递归读取目录)。
e.dataTransfer.files —— 简单、兼容性好、无权限问题items + webkitGetAsEntry()(Chrome/Safari),且需用户授权(fileSystem API 已废弃,仅剩此路径)items 在 Firefox 中对本地文件返回空 getAsFile(),所以不能只依赖它dropZone.addEventListener('drop', (e) => {
e.preventDefault();
const files = e.dataTransfer.files; // ✅ 推荐:稳定获取 FileList
// const items = e.dataTransfer.items; // ⚠️ 要配合 entry 处理,且兼容性差
Array.from(files).forEach(file => {
console.log(file.name, file.size, file.type);
});
});FormData 上传时,fetch 和 XMLHttpRequest 有啥区别?本质没区别,但 fetch 默认不发 Content-Type 头(由浏览器自动设为带 boundary 的 multipart),而 XMLHttpRequest 若手动设了 Content-Type 反而会破坏边界符,导致后端解析失败。
fetch:完全不要设 headers: {'Content-Type': ...},让它自动生成XMLHttpRequest:调用 xhr.send(formdata) 即可,千万别 xhr.setRequestHeader
XMLHttpRequest.upload.onprogress 更直接;fetch 需配合 ReadableStream + TransformStream,复杂度高得多const formData = new FormData();
Array.from(files).forEach(file => formData.append('files', file));
// ✅ 正确:fetch 不设 Content-Type
fetch('/upload', { method: 'POST', body: formData });
// ✅ 正确:XHR 不设 header
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = e => console.log(${(e.loaded / e.total * 100).toFixed(0)}%);
xhr.open('POST', '/upload');
xhr.send(formData);
最易被忽略的是:拖放本身不校验文件类型或大小,accept 属性在拖放中完全无效,所有校验必须在 drop 回调里手动做。前端拦不住恶意文件,但至少别让超大文件卡死浏览器再报错。