松鼠的博客

导航

html5实现文件批量上传组件

一、概述

在html5中,相对于之前添加了不少新的元素和属性,在javascript中也添加了一些新的API,这些给我们的开发带来了很多便利。但由于各浏览器的发展步骤不一致,也导致了不同浏览器对html5支持的差异性。

 

二、实现原理

1.在该html5实现的文件批量上传组件中,我们主要是利用html5中的一些新属性和新API来达到我们的目的。具体如下:

(1)file input控件中的multiple属性。添加了该属性后,我们在选择文件时就可以进行多选。

(2)javascript File API和相应一些属性。利用这些新功能,我们可以获取到选择的文件集合以及文件的一些属性,如文件名,文件大小,文件类型等等。

(3)FormData。使用它可以向服务器提交key/value的键值对数据。

(4)通过XMLHttpRequest的onprogress来获取和更新文件上传进度。

上面就是用到的主要技术点,更多详细的部分可以参考源代码。

2.具体的流程。

(1)首先使用时需要提交选择按钮,上传按钮和列表显示的元素id,组件通过为选择按钮添加onmouseover事件来动态生成(如果尚未生成过)一个file input控件,并设置为透明,然后定位到鼠标可以触发的位置。所以当用户点击选择按钮时,实际上点击的是file input控件的浏览来浏览并选择文件。

(2)组件调用file控件的files来获取文件的集合,然后循环将文件显示成列表,文件名、文件类型、文件大小、文件上传状态等可以通过占位符来确定其显示与否以及显示的位置。

(3)通过为上传按钮添加click上传事件来实现上传,上传时通过FormData和Ajax逐个上传(服务端按照常规获取文件的方法来获取相应的文件并实现上传),并显示上传进度。

三、浏览器兼容性

适用浏览器:Chrome,Firefox,Safari,Opera,IE10 

除了IE之外的其他浏览器开始支持的版本没有详细了解和测试,电脑上都是它们目前的新版本,可以很好的支持。

四、使用方法

具体调用方法如下(说明看注释):

window.onload = function () {
    $ling.html5Upload.config = {
        selectButtonId: "selectButton",
        uploadButtonId: "uploadButton",
        fileProgressContainerId: "processContainer",
        url: "upload.ashx",
        fileTypes: "|.jpg|.jpeg|.txt|",
        fileSize: 300 * 1024 * 1024, //这里的限制 要跟服务端对文件大小的限制一致 或者 小于服务器文件大小的限制
        //回调可以调用三个参数 总数,成功数和失败数,如果不需要可以不传
        callback: function (allCount, successCount, failCount) { alert(allCount + "/" + successCount + "/" + failCount); }
    };
    $ling.html5Upload.init();
};

  上面的配置config中还有下面列出来的5个属性,下面的值均为默认值,不配置时将显示默认值

failWord: "失败",//该文件上传失败时显示在百分比进度地方的提示显示
waitingWord: "等待", //该文件等待上传时显示在百分比进度地方的提示显示
fileTypeInvalidatedWord: "格式不支持", //格式不支持时显示在百分比进度地方的提示显示
fileSizeInvalidatedWord: "文件超出限制", //文件超出限制时时显示在百分比进度地方的提示显示
repeatSubmitWord: "请不要重复提交",//重复提交时的提示

  html代码如下:

<div>
    <input id="selectButton" type="button" value="Select" />
    <input id="uploadButton" type="button" value="Upload" />
    <div id="processContainer" style="display:none;">
        <div>{FileName} | {FileSize} | {FileType} | {UploadPercentage}</div>
    </div>
</div>

具体也可以看下载里面的例子。

五、源代码

具体代码:

(function () {
    //公开方法
    var html5Upload = {
        init: null
    };
    //默认配置
    html5Upload.configDefault = {
        selectButtonId: null,
        uploadButtonId: null,
        fileProgressContainerId: null,
        failWord: "失败",//该文件上传失败时显示在百分比进度地方的提示显示
        waitingWord: "等待", //该文件等待上传时显示在百分比进度地方的提示显示
        fileTypeInvalidatedWord: "格式不支持", //格式不支持时显示在百分比进度地方的提示显示
        fileSizeInvalidatedWord: "文件超出限制", //文件超出限制时时显示在百分比进度地方的提示显示
        repeatSubmitWord: "请不要重复提交", //重复提交时的提示
        fileTypes: null,
        fileSize: null,
        callback: null,
        url:null
    };
    //初始化事件
    html5Upload.init = function () {
        new Upload();
    };
    //上传事件构造函数
    function Upload() {
        this.config = html5Upload.config;
        this.config.failWord = !this.config.failWord ? html5Upload.configDefault.failWord : this.config.failWord;
        this.config.waitingWord = !this.config.waitingWord ? html5Upload.configDefault.waitingWord : this.config.waitingWord;
        this.config.repeatSubmitWord = !this.config.repeatSubmitWord ? html5Upload.configDefault.repeatSubmitWord : this.config.repeatSubmitWord;
        this.config.fileTypeInvalidatedWord = !this.config.fileTypeInvalidatedWord ? html5Upload.configDefault.fileTypeInvalidatedWord : this.config.fileTypeInvalidatedWord;
        this.config.fileSizeInvalidatedWord = !this.config.fileSizeInvalidatedWord ? html5Upload.configDefault.fileSizeInvalidatedWord : this.config.fileSizeInvalidatedWord;
    
        this.successCount = 0;
        this.failCount = 0;
    
        this.createFileInput();
        //绑定选择按钮事件
        addEvent("mousemove", document.getElementById(this.config.selectButtonId), function (obj, event) {
            return function (event) {
                obj.selectButtonMoveEvent(event);
            };
        } (this));
        //绑定上传按钮事件
        addEvent("click", document.getElementById(this.config.uploadButtonId), function (obj) {
            return function () {
                if (obj.isUploaded) {
                    alert(obj.config.repeatSubmitWord);
                    return;
                }
                obj.isUploaded = true;
                obj.uploadFile();
            };
        } (this));
    }
    //创建文件输入框
    Upload.prototype.createFileInput = function () {
        var id = Math.random();
        var fileInputId = ("html5UploadBox" + id).replace("0.", "");
        var fileInput = document.createElement("input");
        fileInput.id = this.fileInputId;
        fileInput.type = "file";
        fileInput.multiple = true;
        fileInput.style.position = "absolute";
        if (fileInput.filters) {
            fileInput.filters.alpha.opactity = 0;
        }
        else {
            fileInput.style.opacity = 0;
        }
        document.body.appendChild(fileInput);
        this.fileInput = fileInput;
        addEvent("change", this.fileInput, function (obj) {
            return function () {
                //重置属性值
                obj.progressPercentage = null;
                obj.isUploaded = false;
                obj.successCount = 0;
                obj.failCount = 0;
                //执行列表显示
                if (!obj.progressTimer) {
                    obj.displayProgress();
                }
            };
        } (this));
    };
    //文件选择按钮事件 将透明的文件输入框至于鼠标点击范围
    Upload.prototype.selectButtonMoveEvent = function (event) {
        var x = event.clientX + document.body.scrollLeft;;
        var y = event.clientY+ document.body.scrollTop;

        this.fileInput.style.top = y - 5 + "px";
        this.fileInput.style.left = x + 5 - this.fileInput.offsetWidth + "px";
    };
    //显示文件列表
    Upload.prototype.displayProgress = function (isNotRepeat) {
        var files = this.fileInput.files;
        var progressContainer = document.getElementById(this.config.fileProgressContainerId);
        var html;
        if (!this.listTemplate) {
            this.listTemplate = progressContainer.innerHTML;
        }
        if (!this.progressPercentage) {
            this.progressPercentage = new Array();
        }
        html = this.listTemplate;
        var dealHTML = "";
        for (var i = 0, len = files.length; i < len; i++) {
            if (!this.progressPercentage[i]) {
                this.progressPercentage[i] = this.config.waitingWord;
            }
            if (!checkFileType(this.config.fileTypes, files[i].name)) {
                this.progressPercentage[i] = this.config.fileTypeInvalidatedWord;
            }
            else if (!checkFileSize(this.config.fileSize, files[i].size)) {
                this.progressPercentage[i] = this.config.fileSizeInvalidatedWord;
            }
            var tempHTML = html.replace(/\{FileName\}/g, files[i].name);
            tempHTML = tempHTML.replace(/\{FileType\}/g, files[i].type);
            tempHTML = tempHTML.replace(/\{FileSize\}/g, getFileSizeString(files[i].size));
            tempHTML = tempHTML.replace(/\{UploadPercentage\}/g, this.progressPercentage[i]);
            dealHTML += tempHTML;
        }
        progressContainer.innerHTML = dealHTML;
        progressContainer.style.display = "";
        if (isNotRepeat) {
            return;
        }
        this.progressTimer = setTimeout(function (obj) {
            return function () {
                obj.displayProgress();
            };
        } (this)
        , 200);
    };
    //上传文件到服务器
    Upload.prototype.uploadFile = function (index) {
        var files = this.fileInput.files;
        var len = files.length;
        var fd = new FormData();
        var request = httpRequest();
        if (!index && index != 0) {
            index = 0;
        }
        //如果不是合法文件则跳过不上传
        if (this.progressPercentage[index] != this.config.waitingWord) {
            index++;
            this.failCount++;
            this.uploadFile(index);
        }
        else {
            fd.append("ling_file_name", files[index]);
            request.open("POST", this.config.url, true);
            request.onprogress = uploadProgressEvent(this, index);
            request.onreadystatechange = uploadSuccessEvent(this, index, request, len);
            request.onerror = uploadFailEvent(this, index);
            request.send(fd);
        }
    };
    
    //上传中的函数
    function uploadProgressEvent(obj, index) {
        return function (event) {
            obj.progressPercentage[index] = parseInt(event.loaded * 100 / event.total) + "%";
        };
    }
    //上成功函数
    function uploadSuccessEvent(obj, index, request, len) {
        return function () {
            if (request.readyState === 4 && request.status === 200) {
                if (request.responseText === "1") {
                    obj.progressPercentage[index] = "100%";
                    obj.successCount++;
                }
                else {
                    obj.progressPercentage[index] = obj.config.failWord;
                    obj.failCount++;
                }
                index++;
                if (index > len - 1) {
                    //结束时清除列表更新定时执行器 然后执行最后一遍
                    clearTimeout(obj.progressTimer);
                    obj.progressTimer = null;
                    obj.displayProgress(true);
                    if (obj.config.callback) {
                        obj.config.callback(len, obj.successCount, obj.failCount);
                    }
                    return;
                }
                obj.uploadFile(index);
            };
        }
    }
    //上传失败函数
    function uploadFailEvent(obj, index) {
        return function () {
            obj.progressPercentage[index] = obj.config.failWord;
            index++;
            obj.uploadFile(index);
        };
    }
    //动态绑定事件
    function addEvent(eventType, element, fn) {
        if (element && eventType && fn) {
            if (window.addEventListener) {
                element.addEventListener(eventType, fn);
            }
            else {
                element.attachEvent("on" + eventType, fn);
            }
        }
    };
    //获取XMLHttpRequest
    function httpRequest() {
        try {
            return new XMLHttpRequest();
        }
        catch (e) {
            return new ActiveXObject("Microsoft.XMLHTTP");
        }
    }
    //转换文件大小为k或者M表示
    function getFileSizeString(size) {
        if (!size && typeof (size) !== "number") {
            return "0k";
        }
        if (size > 1024 * 1024) {
            return (size / (1024 * 1024)).toFixed(2) + "M"
        }
        else {
            return (size / 1024).toFixed(2) + "k";
        }
    }
    //检查文件类型是否合法
    function checkFileType(allowFileTypes, fileName) {
        if (!allowFileTypes) {
            return true;
        }
        if (!fileName || fileName.indexOf('.') < 1) {
            return false;
        }
        var extension = fileName.substring(fileName.lastIndexOf('.'), fileName.length).toLowerCase();
        allowFileTypes = allowFileTypes.toLowerCase();
        if (allowFileTypes.indexOf("|" + extension + "|") > -1) {
            return true;
        }
        return false;
    }
    //检查文件大小是否合法
    function checkFileSize(allowSize, fileSize) {
        if (!allowSize || typeof (allowSize) != "number") {
            return true;
        }
        if (fileSize <= allowSize) {
            return true;
        }
        return false;
    }
    window.$ling = window.$ling || {};
    window.$ling.html5Upload = html5Upload;
    
})();

 

 

参考文章:http://blog.ncmem.com/wordpress/2023/12/25/html5%e5%ae%9e%e7%8e%b0%e6%96%87%e4%bb%b6%e6%89%b9%e9%87%8f%e4%b8%8a%e4%bc%a0%e7%bb%84%e4%bb%b6/

欢迎入群一起讨论

 

 

posted on 2023-12-25 18:27  Xproer-松鼠  阅读(323)  评论(0编辑  收藏  举报