乐趣区

关于html5:H5拖拽上传文件进度条显示

这篇文章利用 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 解决上传文件

整个流程不算简单,能够通过 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,浏览器关上地址能够看到整个流程。

退出移动版