HTML批量文件上传2——进度条显示

作者:私语茶馆

非常多的云应用中需要上传文本,包括图片,文件等等,这些批量文件上传,往往涉及到进度条显示,多文件上传等,这里分享一个非常好的案例,来自BootStrapfriendly.com,方便大家开发产品时使用。

已验证的场景:PHP+JavaScript; Servlet+JavaScript; 使用的环境:Tomcat

1.异步上传文件使用的基本概念

1.1.AJAX

进度条本质是用脚本控制ProgressBar的显示,其中会用到AJAX,AJAX有如下特征:

  • AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
  • AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
  • AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。
  • AJAX 不需要任何浏览器插件,但需要用户允许 JavaScript 在浏览器上执行。
  • XMLHttpRequest 只是实现 Ajax 的一种方式。

1.2. Form

<form id="upload_form" enctype="multipart/form-data" method="post">

enctype="multipart/form-data": 指form中包含二进制的文件形式

1.3.XMLHttpRequest

XMLHttpRequest(XHR)对象用于与服务器后端服务做请求交互,可以在不刷新页面的情况下请求特定的URL,获取数据。允许网元在不影响用户操作的情况下,更新页面的局部内容,是AJAX的一种关键使用绩效。

XMLHttpRequest可以支持多种协议,包括FTP, file等。

如果需要处理消息事件,可以使用EventSource,如果是全双工的可以是WebSocket。

参考:XMLHttpRequest - Web API | MDN (mozilla.org)

1.2.项目结构优化-独立的Scripts脚本

从项目结构上讲,Script最好是独立文件,可以使用<script src="myscripts.js"></script>来引入,这样可以保持HTML简洁。

如下图所示:

创建Scripts脚本

HTML引用该脚本

Script脚本

注意事项:要独立一个InitialLoad,并通过window.οnlοad=initialLoad来指定,如果脚本不起作用,可以检查一下windows.onload有没有设置。

2.异步批量上传文件及进度条案例

这里前台部分直接使用Bootstrapfriedly.com的案例,后台用的是Apach的fileupload组件实现多文件上传,URL部分不同。

2.1. 前台界面

<div class="ath_container tile-container ">
    <div id="uploadStatus"></div>
    <h2 style="margin-bottom:10px">AJAX File Upload with Progress Bar using JavaScript</h2>
    <input type="file" id="fileUpload" multiple placeholder="choose file or browse" /> <!-- Add 'multiple' attribute for multiple file selection -->
    <br>
    <br>
    <button onclick="uploadFiles()">Upload</button> <!-- Change function name -->
    <div>
        <table id="progressBarsContainer">
            <!-- Table rows will be dynamically added here -->
        </table>
    </div> <!-- Container for progress bars -->
    <br>
</div>

说明点:

(1)这里没有直接使用Form,用button的Click来处理,后面在Script中使用AJAX的XMLHttpRequest提交表单。

(2)table id=progressBarsContainer做为占位符号,后续ajax发送请求后,用于增量显示图片文件状态用。

2.2. CSS风格文件

* {
    margin: 0px;
    padding: 0px;
    box-sizing: border-box;
}



body {
    font-family: -apple-system, BlinkMacSystemFont, Roboto, Segoe UI,
        Helvetica Neue, Helvetica, Arial, sans-serif;
    margin: 0 auto;
    -webkit-font-smoothing: antialiased;
    box-sizing: border-box;
    color: 
#2f2f2f;
    line-height: 1.5;
}

.ath_container {
    width: 740px;
    margin: 20px auto;
    padding: 0px 20px 0px 20px;
}

.ath_container {
    width: 820px;
    border: 
#d7d7d7 1px solid;
    border-radius: 5px;
    padding: 10px 20px 10px 20px;
    box-shadow: 0 0 5px 
rgba(0, 0, 0, .3);
    /* border-radius: 5px; */
}

#uploadStatus {
    color: 
#00e200;
}

.ath_container a {
    text-decoration: none;
    color: 
#2f20d1;
}

.ath_container a:hover {
    text-decoration: underline;
}

.ath_container img {
    height: auto;
    max-width: 100%;
    vertical-align: middle;
}


.ath_container .label {
    color: 
#565656;
    margin-bottom: 2px;
}



.ath_container .message {
    padding: 6px 20px;
    font-size: 1em;
    color: 
rgb(40, 40, 40);
    box-sizing: border-box;
    margin: 0px;
    border-radius: 3px;
    width: 100%;
    overflow: auto;
}

.ath_container .error {
    padding: 6px 20px;
    border-radius: 3px;
    background-color: 
#ffe7e7;
    border: 1px solid 
#e46b66;
    color: 
#dc0d24;
}

.ath_container .success {
    background-color: 
#48e0a4;
    border: 
#40cc94 1px solid;
    border-radius: 3px;
    color: 
#105b3d;
}

.ath_container .validation-message {
    color: 
#e20900;
}

.ath_container .font-bold {
    font-weight: bold;
}

.ath_container .display-none {
    display: none;
}

.ath_container .inline-block {
    display: inline-block;
}

.ath_container .float-right {
    float: right;
}

.ath_container .float-left {
    float: left;
}

.ath_container .text-center {
    text-align: center;
}

.ath_container .text-left {
    text-align: left;
}

.ath_container .text-right {
    text-align: right;
}

.ath_container .full-width {
    width: 100%;
}

.ath_container .cursor-pointer {
    cursor: pointer;
}

.ath_container .mr-20 {
    margin-right: 20px;
}

.ath_container .m-20 {
    margin: 20px;
}



.ath_container table {
    border-collapse: collapse;
    border-spacing: 0;
    width: 100%;
    border: 1px solid 
#ddd;
    margin-top: 20px;
}


.ath_container table th,
.ath_container table td {
    text-align: left;
    padding: 5px;
    border: 1px solid 
#ededed;
    width: 50%;
}

tr:nth-child(even) {
    background-color: 
#f2f2f2
}

.ath_container .progress {
    margin: 20px 0 0 0;
    width: 300px;
    border: 1px solid 
#ddd;
    padding: 5px;
    border-radius: 5px;
}

.ath_container .progress-bar {
    width: 0%;
    height: 24px;
    background-color: 
#4CAF50;
    margin-top: 15px;
    border-radius: 12px;
    text-align: center;
    color: 
#fff;
}

@media all and (max-width: 780px) {
    .ath_container {
        width: auto;
    }
}


.ath_container input,
.ath_container textarea,
.ath_container select {
    box-sizing: border-box;
    width: 200px;
    height: initial;
    padding: 8px 5px;
    border: 1px solid 
#9a9a9a;
    border-radius: 4px;
}

.ath_container input[type="checkbox"] {
    width: auto;
    vertical-align: text-bottom;
}

.ath_container textarea {
    width: 300px;
}

.ath_container select {
    display: initial;
    height: 30px;
    padding: 2px 5px;
}

.ath_container button,
.ath_container input[type=submit],
.ath_container input[type=button] {
    padding: 8px 30px;
    font-size: 1em;
    cursor: pointer;
    border-radius: 25px;
    color: 
#ffffff;
    background-color: 
#6213d3;
    border-color: 
#9554f1 
#9172bd 
#4907a9;
}

.ath_container input[type=submit]:hover {
    background-color: 
#f7c027;
}

::placeholder {
    color: 
#bdbfc4;
}

.ath_container label {
    display: block;
    color: 
#565656;
}

@media all and (max-width: 400px) {
    .ath_container {
        padding: 0px 20px;
    }

    .ath_container {
        width: auto;
    }

    .ath_container input,
    .ath_container textarea,
    .ath_container select {
        width: 100%;
    }
}

2.3. JavaScript脚本

    function uploadFiles() {
        var fileInput = document.getElementById('fileUpload');
        var files = fileInput.files;

        //(1)校验图片文件,并上传
        for (var i = 0; i < files.length; i++) {
            var allowedExtensions = ['.jpg', '.jpeg', '.png', '.pdf', '.svg', '.zip', '.docx', '.xlsx'];
            var fileExtension = files[i].name.substring(files[i].name.lastIndexOf('.')).toLowerCase();

            if (allowedExtensions.includes(fileExtension)) {
                //(1.1)一次上传一个文件,并显示文件名和进度
                uploadFile(files[i]);
            } else {
                alert('Invalid file type: ' + fileExtension);
            }
        }
    }

    function uploadFile(file) {
        var formData = new FormData();
        formData.append('file', file);

        var progressBarContainer = document.createElement('div'); // Container for progress bar and file name
        progressBarContainer.className = 'progress-container';

        var fileName = document.createElement('div'); // Display file name
        fileName.className = 'file-name';
        fileName.textContent = file.name;
        //progressBarContainer.appendChild(fileName);

        var progressBar = document.createElement('div'); // Create a new progress bar element
        progressBar.className = 'progress-bar';
        progressBar.id = 'progressBar_' + file.name;

        progressBarContainer.appendChild(progressBar);

        var progressBarsContainer = document.getElementById('progressBarsContainer');

        var newRow = document.createElement('tr'); // Create a new table row
        var newCell = document.createElement('td'); // Create a new table cell
        var newCell2 = document.createElement('td'); // Create a new table cell
        newCell.appendChild(fileName);
        newCell2.appendChild(progressBarContainer);
        newRow.appendChild(newCell);
        newRow.appendChild(newCell2);
        progressBarsContainer.appendChild(newRow);

        var xhr = new XMLHttpRequest();

        xhr.upload.addEventListener('progress', function(event) {
            if (event.lengthComputable) {
                var percent = Math.round((event.loaded / event.total) * 100);
                progressBar.style.width = percent + '%';
                progressBar.innerHTML = percent + '%';
            }
        });

        xhr.addEventListener('load', function(event) {
            var uploadStatus = document.getElementById('uploadStatus');
            uploadStatus.innerHTML = event.target.responseText;
            // Reset the input field of type "file"
            document.getElementById('fileUpload').value = '';

        });

        xhr.open('POST', 'upload.do', true);
        xhr.send(formData);
    }

关键点:

  • 这里的文件上传是一个一个上传的,上传一个显示一个状态,上传完成后,显示全部文件的状态。
  • xhr.open('post', 'upload.do',true); URL需要看本身后台发布的URL,我这里测试的是一个Servlet后台。

3.效果图

上传前

上传后

4.相关章节

HTML批量文件上传1------图像预览方式_html上传多张图片并预览-CSDN博客

完整的项目下载(待后续补充):

相关推荐
m0_7482361118 分钟前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo61730 分钟前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_7482489432 分钟前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_7482356144 分钟前
从零开始学前端之HTML(三)
前端·html
旭久2 小时前
SpringBoot的Thymeleaf做一个可自定义合并td的pdf表格
pdf·html·springboot
一个处女座的程序猿O(∩_∩)O3 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink6 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者7 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-8 小时前
验证码机制
前端·后端
燃先生._.9 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js