在日常開(kāi)發(fā)中,我們經(jīng)??吹揭恍┚W(wǎng)站支持“拖拽上傳”功能,比如你打開(kāi)某個(gè)文件管理后臺(tái),可以直接把本地文件拖進(jìn)頁(yè)面,它就自動(dòng)上傳了。
這個(gè)體驗(yàn)非常絲滑,也顯得“高級(jí)”。
但你有沒(méi)有想過(guò):這個(gè)功能到底是怎么實(shí)現(xiàn)的?
今天我們就來(lái)一起拆解一下:JS 拖拽上傳的完整原理與實(shí)現(xiàn)方式。
?一、什么是拖拽上傳?
簡(jiǎn)單來(lái)說(shuō),拖拽上傳 就是讓用戶可以拖動(dòng)文件到瀏覽器頁(yè)面的某個(gè)區(qū)域,自動(dòng)觸發(fā)上傳,而不是通過(guò) <input type="file">
的傳統(tǒng)點(diǎn)擊方式。
雖然本質(zhì)上最終還是上傳文件,但用戶交互方式變了,這就涉及到瀏覽器的一套“拖放事件機(jī)制”。
??二、核心原理拆解
想實(shí)現(xiàn)拖拽上傳,需要用到以下幾個(gè)關(guān)鍵點(diǎn):
1. 拖拽事件的監(jiān)聽(tīng)
瀏覽器提供了一系列拖拽相關(guān)事件:
| |
dragenter | 拖進(jìn)目標(biāo)區(qū)域時(shí)觸發(fā) |
dragover | 拖動(dòng)過(guò)程中持續(xù)觸發(fā)(必須阻止默認(rèn)) |
dragleave | 拖出目標(biāo)區(qū)域時(shí)觸發(fā) |
drop | 在目標(biāo)區(qū)域松開(kāi)鼠標(biāo)時(shí)觸發(fā)(即“放下”) |
2. 阻止默認(rèn)行為(非常關(guān)鍵)
瀏覽器默認(rèn)會(huì)在 drop
時(shí)打開(kāi)文件,所以我們必須阻止默認(rèn)行為:
e.preventDefault();
否則你一拖文件進(jìn)頁(yè)面,頁(yè)面就直接跳轉(zhuǎn)去預(yù)覽了...
3. 從 drop
中讀取文件
當(dāng)你在 drop
事件里獲取到事件對(duì)象 e
時(shí),可以通過(guò):
e.dataTransfer.files
來(lái)讀取用戶拖進(jìn)來(lái)的文件列表。
這和傳統(tǒng) input 方式中的 input.files
是一樣的東西,只不過(guò)來(lái)源不同。
???三、完整實(shí)現(xiàn)思路(HTML + JS)
下面我們來(lái)寫(xiě)個(gè)最簡(jiǎn)版的拖拽上傳 DEMO,只用于展示流程:
<div id="drop-area" style="width: 300px; height: 200px; border: 2px dashed #ccc; text-align: center; line-height: 200px;">
拖拽文件到這里上傳
</div>
<script>
const dropArea = document.getElementById('drop-area');
// 拖進(jìn)來(lái):添加高亮
dropArea.addEventListener('dragenter', (e) => {
e.preventDefault();
dropArea.style.borderColor = '#3eaf7c';
});
// 拖動(dòng)中:必須阻止默認(rèn),才能觸發(fā) drop
dropArea.addEventListener('dragover', (e) => {
e.preventDefault();
});
// 拖出:恢復(fù)樣式
dropArea.addEventListener('dragleave', (e) => {
e.preventDefault();
dropArea.style.borderColor = '#ccc';
});
// 放下文件:讀取文件
dropArea.addEventListener('drop', (e) => {
e.preventDefault();
dropArea.style.borderColor = '#ccc';
const files = e.dataTransfer.files;
// 遍歷文件列表
for (let file of files) {
console.log('文件名:', file.name);
uploadFile(file); // 上傳邏輯
}
});
// 模擬上傳函數(shù)
functionuploadFile(file) {
const formData = newFormData();
formData.append('file', file);
fetch('/api/upload', {
method: 'POST',
body: formData
}).then(res => {
if (res.ok) {
console.log(`${file.name} 上傳成功`);
} else {
console.error(`${file.name} 上傳失敗`);
}
});
}
</script>
?四、進(jìn)階點(diǎn)補(bǔ)充
1. 拖拽區(qū)域可以是任意 DOM 元素,不一定是整個(gè)頁(yè)面。
你甚至可以讓 <textarea>
接收拖拽的文件(比如 markdown 圖片粘貼上傳)。
2. e.dataTransfer.items
可以識(shí)別是不是文件夾
for (let item of e.dataTransfer.items) {
if (item.kind === 'file') {
const entry = item.webkitGetAsEntry();
if (entry.isDirectory) {
console.log('是個(gè)文件夾');
}
}
}
3. 拖拽上傳配合組件庫(kù)用法(如 Vue + Element Plus)
Element Plus 的 <el-upload>
組件就內(nèi)置了 drag 模式,原理跟我們上面說(shuō)的是一樣的,只是封裝好了。
??五、總結(jié)
我們回顧一下拖拽上傳的核心實(shí)現(xiàn):
- 1. 監(jiān)聽(tīng)
dragenter
, dragover
, drop
等事件; - 2. 阻止默認(rèn)行為,避免瀏覽器打開(kāi)文件;
- 3. 從
e.dataTransfer.files
讀取文件; - 4. 手動(dòng)構(gòu)造
FormData
上傳文件; - 5. 增強(qiáng)體驗(yàn):拖拽高亮、上傳進(jìn)度、錯(cuò)誤提示等。
雖然概念簡(jiǎn)單,但這就是一個(gè)非常實(shí)用的 Web 技能,做后臺(tái)系統(tǒng)、CMS、富文本編輯器、前端可視化工具時(shí)經(jīng)常會(huì)用到。