這篇文章利用H5的拖放API,實現拖拽文件到瀏覽器上傳的功能。css
業務需求:html
一、拖拽上傳文件
到瀏覽器可上傳區域
。
二、上傳文件
停留在可上傳區域
,提示信息可上傳。
三、釋放上傳文件
,釋放位置在可上傳區域
,執行上傳操做,顯示上傳進度。node
HTML 的 drag & drop 使用了 DOM event model 以及從 mouse events 繼承而來的 drag events 。一個典型的拖拽操做是這樣的:用戶選中一個可拖拽的(draggable)元素,並將其拖拽(鼠標不放開)到一個可放置的(droppable)元素,而後釋放鼠標。
根據業務需求,這裏將使用到的拖拽事件包括:express
dragover
文件被進入到可上傳區域時觸發dragleave
拖拽文件離開可上傳區域時觸發drop
拖拽文件在可上傳區域釋放時觸發接下來編寫可上傳區域的dom元素:npm
<div id="target"> <p class="tip">拖拽文件到此處上傳</p> </div>
添加事件:json
const dragEl=document.getElementById("target"); dragEl.addEventListener("dragover",handleOver) dragEl.addEventListener("drop",handleDrop) dragEl.addEventListener("dragleave",handleLeave) // 釋放在目標區域 function handleDrop(ev){ ev.preventDefault(); // 獲取釋放文件 console.log(ev.dataTransfer.files[0]) } function handleOver(ev){ ev.preventDefault(); console.log("進入目標區域") } function handleLeave(ev){ ev.preventDefault(); console.log("離開目標區域") }
添加的拖拽事件都要先阻止瀏覽器默認事件
良好的提示信息能夠讓用戶有更好的使用體驗,接下來給上傳區域添加提示信息:後端
<div class="u init" id="target"> <p class="tip-start">拖拽文件到此處上傳</p> <p class="tip-over">鬆開上傳</p> <p class="tip-uploading">上傳中...</p> <p class="tip-done">上傳成功</p> <p class="tip-error">上傳失敗</p> </div>
經過樣式控制信息的顯示隱藏:瀏覽器
.u{ width: 100%; height: 95vh; display: flex; align-items: center; justify-content: center; transition:background .3s; } .u [class^="tip"]{ opacity: .5; font-size: 23px; text-align: center; } .u [class^="tip"]{ display: none; } .u.init{ background-color: #f0f0f0; } .u.init .tip-start{ display: block; } .u.actived{ background-color: #f9f9f9; border: 1px dashed #ddd; } .u.actived .tip-over{ display: block; } .u.uploading{ background-color: #ddd; } .u.uploading .tip-uploading{ display: block; } .u.success{ background-color: #f5f5f5; } .u.success .tip-done{ display: block; } .u.error{ background-color: #ffd0d0; } .u.error .tip-error{ display: block; }
這裏經過給target
元素添加不一樣的類名,控制不一樣信息的樣式,以下:app
className=".u.init"
初始狀態className=".u.actived"
目標進入可上傳區域className=".u.uploading"
文件正在上傳className=".u.success"
文件上傳成功className=".u.error"
文件上傳失敗接下來給拖拽事件添加上對應的顯示類名:dom
function handleDrop(ev){ ev.preventDefault(); dragEl.className="u uploading" ... } function handleOver(ev){ ev.preventDefault(); dragEl.className="u actived" } function handleLeave(ev){ ev.preventDefault(); dragEl.className="u init" }
當文件在上傳區域釋放以後,獲取到上傳文件信息,利用FormData對象生成表單數據,接着執行上傳操做。
function handleDrop(ev){ ev.preventDefault(); dragEl.className="u uploading" // 生成表單信息 const fd = new FormData(); const file = ev.dataTransfer.files[0]; fd.append("file",file); // 上傳文件函數 upload(fd) }
生成XMLHttpRequest
發送表單信息給後端:
function upload(data){ const xhr = new XMLHttpRequest(); // 上傳方法和路徑 xhr.open("POST","/upload"); xhr.responseType = "json"; xhr.onload = function(){ if(xhr.response && xhr.response.success){ // 上傳成功後,顯示成功樣式 dragEl.className="u success" // 恢復初始狀態 setTimeout(()=>dragEl.className="u init",3000); }else{ // 上傳失敗 dragEl.className="u error"; console.error(xhr.response.error); } } // 發送 xhr.send(data); }
爲了防止沒必要要的bug產生,這裏執行上傳操做以前還須要作一些判斷:
function handleDrop(ev){ ev.preventDefault(); // 還有文件在上傳 if(dragEl.className.indexOf("uploading")>=0) return; ... const MAX_SIZE = 200 * 1024 * 1024; if(file.size>= MAX_SIZE) return alert("文件大於 200mb"); }
經過xhr.upload.onprogress
能夠獲取到文件的上傳進度信息:
xhr.upload.onprogress=function({loaded,total}){ const precent = (loaded/total)*100; console.log(precent) }
給dom元素添加進度條:
<div class="tip-uploading"> <p>上傳中...</p> <p class="progress"><span class="line" id="precent"></span></p> </div>
添加進度條樣式:
.progress{ height: 5px; width: 300px; overflow: hidden; position: relative; border: 1px solid #999; } .line{ top: 0; left: 0; width: 100%; height: 100%; transition:.1s; position: absolute; background-color: #999; transform: translateX(-100%); }
進度條onprogress
事件:
function upload(data){ ... //進度條元素 const precentEl = document.getElementById("precent"); xhr.upload.onprogress=function({loaded,total}){ const precent = (loaded/total)*100; // 進度位置 precentEl.style.transform = `translateX(-${100-precent}%)` // 完成上傳後,顯示上傳成功信息 if(precent>=100) setTimeout(() => dragEl.className="u success", 500); } ... }
整個流程不算複雜,能夠經過express
和multer
模擬整個流程。
打開控制檯,初始化npm
文件:
npm init -y
安裝express
和multer
:
npm install express multer --save-dev
文件目錄:
/- package.json /- server.js /- index.html /- uploads
server.js
const { resolve } = require("path"); const express = require("express"); const app = express(); const multer = require("multer"); const upload = multer({ dest: resolve(__dirname, "./uploads") }); app.use(express.static(__dirname)); app.post("/upload", upload.single("file"), (req, res) => { res.send({ success: true, message: req.file }); }); app.listen(3000, () => console.log(`Serving on localhost:3000`));
index.html
:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>拖拽上傳+進度條顯示</title> <style> .u{ width: 100%; height: 95vh; display: flex; align-items: center; justify-content: center; transition:background .3s; } .u.init{ background-color: #f0f0f0; } .u.init .tip-start{ display: block; } .u.actived{ background-color: #f9f9f9; border: 1px dashed #ddd; } .u.actived .tip-over{ display: block; } .u.uploading{ background-color: #ddd; } .u.uploading .tip-uploading{ display: block; } .u.success{ background-color: #f5f5f5; } .u.success .tip-done{ display: block; } .u.error{ background-color: #ffd0d0; } .u.error .tip-error{ display: block; } .u [class^="tip"]{ opacity: .5; font-size: 23px; text-align: center; } .u [class^="tip"]{ display: none; } .progress{ height: 5px; width: 300px; overflow: hidden; position: relative; border: 1px solid #999; } .line{ top: 0; left: 0; width: 100%; height: 100%; transition:.1s; position: absolute; background-color: #999; transform: translateX(-100%); } </style> </head> <body> <div class="u init" id="target"> <p class="tip-start">拖拽文件到此處上傳</p> <p class="tip-over">鬆開上傳</p> <div class="tip-uploading"> <p>上傳中...</p> <p class="progress"><span class="line" id="precent"></span></p> </div> <p class="tip-done">上傳成功</p> <p class="tip-error">上傳失敗</p> </div> <script> const dragEl=document.getElementById("target"); dragEl.addEventListener("dragover",handleOver) dragEl.addEventListener("drop",handleDrop) dragEl.addEventListener("dragleave",handleLeave) function handleDrop(ev){ ev.preventDefault(); // 還有文件正在上傳 if(dragEl.className.indexOf("uploading")>=0) return; dragEl.className="u uploading" const file = ev.dataTransfer.files[0]; const fd = new FormData(); fd.append("file",file); upload(fd) } function handleOver(ev){ ev.preventDefault(); dragEl.className="u actived" } function handleLeave(ev){ ev.preventDefault(); dragEl.className="u init" } function upload(data){ const xhr = new XMLHttpRequest(); xhr.open("POST","/upload"); xhr.responseType = "json"; const precentEl = document.getElementById("precent"); xhr.upload.onprogress=function({loaded,total}){ const precent = (loaded/total)*100; precentEl.style.transform = `translateX(-${100-precent}%)` if(precent>=100) setTimeout(() => dragEl.className="u success", 500); } xhr.onload = function(){ if(xhr.response && xhr.response.success){ setTimeout(()=>dragEl.className="u init",3000); }else{ dragEl.className="u error"; console.error(xhr.response.error); } } xhr.send(data); } </script> </body> </html>
控制檯執行node server.js
,瀏覽器打開地址能夠看到整個流程。