这篇文章利用H5的拖放API,实现拖拽文件到浏览器上传的性能。
业务需要:
1、拖拽上传文件
到浏览器可上传区域
。
2、上传文件
停留在可上传区域
,提示信息可上传。
3、开释上传文件
,开释地位在可上传区域
,执行上传操作,显示上传进度。
拖放事件
HTML 的 drag & drop 应用了 DOM event model 以及从 mouse events 继承而来的 drag events 。一个典型的拖拽操作是这样的:用户选中一个可拖拽的(draggable)元素,并将其拖拽(鼠标不放开)到一个可搁置的(droppable)元素,而后开释鼠标。
依据业务需要,这里将应用到的拖拽事件包含:
dragover
文件被进入到可上传区域时触发dragleave
拖拽文件来到可上传区域时触发drop
拖拽文件在可上传区域开释时触发
接下来编写可上传区域的dom元素:
<div id="target"> <p class="tip">拖拽文件到此处上传</p></div>
增加事件:
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
元素增加不同的类名,管制不同信息的款式,如下:
className=".u.init"
初始状态className=".u.actived"
指标进入可上传区域className=".u.uploading"
文件正在上传className=".u.success"
文件上传胜利className=".u.error"
文件上传失败
接下来给拖拽事件增加上对应的显示类名:
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 解决上传文件
整个流程不算简单,能够通过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
,浏览器关上地址能够看到整个流程。