H5拖拽上傳文件+進度條顯示

這篇文章利用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 處理上傳文件

整個流程不算複雜,能夠經過expressmulter模擬整個流程。

打開控制檯,初始化npm文件:

npm init -y

安裝expressmulter

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,瀏覽器打開地址能夠看到整個流程。

相關文章
相關標籤/搜索