html5 文件系统File API

前言:

在做浏览器上传图片的时候,一般采用form表单上传,这种上传无法预览图片,无法查看图片大小,无法知道图片的类型等等!那么在html5 File API提供了这些表单无法实现的功能,而且还支持拖拽上传!现在我们开始学习。。

一、File Api 浏览器支持检测

File Api给js提供了以下几个接口来访问本地文件系统:

1、File - 单个文件;提供了诸如name、file size、mimetype(媒体类型)等只读文件属性。
2、FileList - 一个类数组File对象集合;
3、Blob  - 文件对象的二进制原始数据;
File API还提供了一个异步读取文件的接口 - FileReader。

现在我们开始检测浏览器兼容情况:

if(window.File && window.FileReader && window.FileList && window.Blob) {
    alert('您的浏览器支持所有的file api');
} else {
    alert('您的浏览器不支持文件上传!请先升级您的浏览器!');
}

File API主要是用来获取本地文件系统中文件的reference(引用对象),通过File API我们可以获得一个代表本地文件的js对象,而FileReader通过该File对象即可异步地读取本地文件的内容。

二、form表单的file控件

在html5中,file控件支持选择多个文件,用户选择了某些文件之后,html5为我们提供了一个访问这些文件的对象 - FileList,这是一个类数组集合,每一个元素为一个File对象,File对象中包含了文件的所有可访问信息。

示例的HTML代码如下:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>图片上传--多文件上传title>
head>
<body>
    <input type="file" id="Files" name="files[]" multiple />
    <div id="Lists">div>
    
body>
html>
<script>
    function fileSelect(e) {
        e = e || window.event;
        var files = e.target.files; //FileList 对象 
        var output = [];
        for(var i = 0, f; f = files[i]; i++) {
            output.push('
  • ' + f.name + '(' + f.type + ') - ' + f.size +' bytes
  • '); document.getElementById('Lists').innerHTML = '
      ' + output.join('') + '
    '; } } if(window.File && window.FileList && window.FileReader && window.Blob) { document.getElementById('Files').addEventListener('change', fileSelect, false); } else { document.write('您的浏览器不支持File Api'); } script>

    由以上代码可以看到,html5为file这个dom元素新增了files接口(e.target指向了file input元素,实际上也可以用this来访问,即this.files),得到的就是FileList,通过遍历该集合,即可访问到各个已选择的文件对象。


    三、Drag And Drop API

     除了可以通过表单file控件访问本地文件外,还可以通过拖放API来访问。

    html5还提供了一个更快捷的方式来触发读取文件的时机,前面我们已经说过,浏览器不能主动地访问本地操作系统,只能依赖于用户行为,用户想要访问文件时,才去访问本地文件系统。
    拖放API能做的事情很多,在这里我只介绍一下它在访问本地文件方面的功能。

    示例代码:

    function fileDrop(e) {
        e = e || window.event;
        e.stopPropagation(); // 阻止冒泡
        e.preventDefault(); //阻止默认行为
        var files = e.dataTransfer.files; //FileList
        var output = [];
        for(var i = 0, f; f = files[i]; i++) {
            output.push('
  • ' + f.name + '(' + f.type + ') - ' + f.size +' bytes
  • '); } document.getElementById('Lists').innerHTML = '
      ' + output.join('') + '
    '; }; function dragOver(e) { e = e || window.event; e.stopPropagation(); e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; //指定拖放视觉效果 }; var d = document.getElementById('DropZone'); try { d.addEventListener('dragover', dragOver, false); d.addEventListener('drop', fileDrop, false) } catch(ex) { document.write('something must be wrong!'); }

    由以上代码可以看到,拖放api是以event对象的dataTransfer对象作为数据载体,该对象具有一个files属性,通过该属性,可以拿到我们想要的FileList集合。后面将介绍FileReader,通过它具体地读取文件内容。

    更多关于dataTransfer

    拖拽事件

      通过拖拽事件,咱们就可以控制拖拽很多东西了。其中什么元素或者是哪里发生了拖拽事件是最关键的。有些事件是在被拖动的元素上触发,有些事件是在放置目标上触发的。

    拖动某元素时候,触发的事件有:dragstart事件、drag事件和dragend事件。

      按下鼠标键并开始移动鼠标的时候,会在被拖拽的元素上触发dragstart事件。这时候光标变成”不能放”符号(圆环中有一条反斜线),表示不能把元素放在自己上门。拖拽开始时,可以通过ondragstart事件处理程序运行JavaScript代码。

      触发dragstart事件后,随即会触发drag事件,而在元素被拖动期间会持续触发drag事件。这个事件与mousemove和touchmove事件类似。当拖动停止时(无论把元素放到了有效的放置目标,还是放到了无效的放置目标上),都会发生dragend事件。

      上面说的三个事件的目标都是被拖动的元素触发。默认情况下,浏览器不会再拖动期间改变被拖动元素的外观。但是可以自行修改。不过,大多数浏览器会为正被拖动的元素创建一个半透明的副本,这个副本始终跟随光标移动。当某个元素被拖动到一个有效的放置

    目标的时候,会触发的事件有:dragenter事件、dragover事件和dragleave或者drop事件。

      只要有元素被拖动到放置目标上,就会触发dragenter事件(类似于mouseover事件)。紧随其后的是dragover事件,而且在被拖动的元素还在放置目标的范围内移动是,会连续触发dragover事件。如果元素被拖出放置目标,dragover事件不再发生,但是会触发

    dragleave事件(类似于mouseout事件)。如果元素被放到了放置目标中会触发drop事件而不是dragleave事件。dragenter事件、dragover事件和dragleave或者drop事件的目标都是作为放置目标的元素。

    读取文件

    HTML5提供了一个叫FileReader的接口,用于异步读取文件内容,它主要定义了以下几个方法:
    readAsBinaryString(File|Blob)
    readAsText(File|Blob [, encoding])
    readAsDataURL(File|Blob)
    readAsArrayBuffer(File|Blob)

    FileReader还提供以下事件:
    onloadstart
    onprogress
    onload
    onabort
    onerror
    onloadend
    一旦调用了以上某个方法读取文件后,我们可以监听以上任何一个事件来获得进度、结果等。

     预览本地图片

    这里主要用到FileReader的readAsDataURL方法,通过将图片数据读取成Data URI的方法,将图片展示出来

    function fileSelect2(e) {
        e = e || window.event;
        var files = this.files;
        var p = document.getElementById('preview2');
    
        for(var i = 0, f; f = files[i]; i++) {
            var reader = new FileReader();
            reader.onload = (function(file) {
                return function(e) {
                    var span = document.createElement('span');
                    span.innerHTML = 'this.result +'" alt="'+ file.name +'" />';
    
                    p.insertBefore(span, null);
                };
            })(f);
            //读取文件内容
            reader.readAsDataURL(f);
        }
    }
    document.getElementById('files2').addEventListener('change', fileSelect2, false);

    完整代码如下:

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>图片上传--预览title>
    head>
    <body>
        <p><input type="file" id="files2" accept="image/*" multiple="">p>   
        <div id="preview2">div>
    body>
    html>
    <script type="text/javascript">
    (function() {
        function fileSelect2(e) {
            e = e || window.event;
            var files = this.files;
            var p = document.getElementById('preview2');
    
            for(var i = 0, f; f = files[i]; i++) {
                var reader = new FileReader();
                reader.onload = (function(file) {
                    return function(e) {
                        var span = document.createElement('span');
                        span.innerHTML = ''+ this.result +'" alt="'+ file.name +'" />';
    
                        p.insertBefore(span, null);
                    };
                })(f);
                //读取文件内容
                reader.readAsDataURL(f);
            }
        }
         document.getElementById('files2').addEventListener('change', fileSelect2, false);
    })();
    script>

    PS:由于需要在页面上预览文本,所以则需要对文件中的html特殊字符进行实体编码,避免浏览器解析文件中的html代码。

    监控读取进度

    既然FileReader是异步读取文件内容,那么就应该可以监听它的读取进度。

    事实上,FileReader的onloadstart以及onprogress等事件,可以用来监听FileReader的读取进度。
    在onprogress的事件处理器中,有一个ProgressEvent对象,这个事件对象实际上继承了Event对象,提供了三个只读属性:
    lengthComputable
    loaded
    total
    通过以上几个属性,即可实时显示读取进度,不过需要注意的是,此处的进度条是针对单次读取的进度,即一次readAsBinaryString等方法的读取进度。

    var input4 = document.getElementById('file4');
    var bar = document.getElementById('progress-bar');
    var progress = document.getElementById('progress');
    function startHandler(e) {
        bar.style.display = 'block';
    }
    function progressHandler(e) {
        var percentLoaded = Math.round((e.loaded / e.total) * 100);
        if (percentLoaded < 100) {
            progress.style.width = percentLoaded + '%';
            progress.textContent = percentLoaded + '%';
        }
    }
    function loadHandler(e) {
        progress.textContent = '100%';
        progress.style.width = '100%';
    }
    function fileSelect4(e) {
        var file = this.files[0];
        if(!file) {
            alert('请选择文件!');
            return false;
        }
        if(file.size > 500 * 1024 * 1024) {
            alert('文件太大,请选择500M以下文件,防止浏览器崩溃!');
            return false;
        }
        progress.style.width = '0%';
        progress.textContent = '0%';
        var reader = new FileReader();
        reader.onloadstart = startHandler;
        reader.onprogress = progressHandler;
        reader.onload = loadHandler;
        reader.readAsBinaryString(this.files[0]);
    }
    input4.onchange = fileSelect4;

    完整html代码如下:

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>上传进度条title>
    head>
    <body>
        <p><input id="file4" type="file">p>
        <div id="progress-bar" style="border:1px solid #333;padding:5px;color:#fff;display:none;">
        <div style="width:0%;height:100%;background:blue;white-space:nowrap;" id="progress">div>
    div>
    body>
    html>
    <script type="text/javascript">
    (function() {
        var input4 = document.getElementById('file4');
        var bar = document.getElementById('progress-bar');
        var progress = document.getElementById('progress');
        function startHandler(e) {
            bar.style.display = 'block';
        }
        function progressHandler(e) {
            var percentLoaded = Math.round((e.loaded / e.total) * 100);
            if (percentLoaded < 100) {
                progress.style.width = percentLoaded + '%';
                progress.textContent = percentLoaded + '%';
            }
        }
        function loadHandler(e) {
            progress.textContent = '100%';
            progress.style.width = '100%';
        }
        function fileSelect4(e) {
            var file = this.files[0];
            if(!file) {
                alert('请选择文件!');
                return false;
            }
            if(file.size > 500 * 1024 * 1024) {
                alert('文件太大,请选择500M以下文件,防止浏览器崩溃!');
                return false;
            }
            progress.style.width = '0%';
            progress.textContent = '0%';
            var reader = new FileReader();
            reader.onloadstart = startHandler;
            reader.onprogress = progressHandler;
            reader.onload = loadHandler;
            reader.readAsBinaryString(file);
        }
        
        input4.onchange = fileSelect4;
        
    })();
    script>

    分割文件

    有的时候,一次性将一个大文件读入内存,并不是一个很好的选择(如果文件太大,可能直接导致浏览器崩溃),上述的监听进度示例就有可能在文件太大的情况下崩溃。

    更稳健的方法是分段读取!

    分段读取文件

    HTML5 File Api提供了一个slice方法,允许分片读取文件内容。

    function readBlob(start, end) {
        var files = document.getElementById('file5').files;
    
        if(!files.length) {
            alert('请选择文件');
            return false;
        }
    
        var file = files[0],
            start = parseInt(start, 10) || 0,
            end = parseInt(end, 10) || (file.size - 1);
    
        var r = document.getElementById('range'),
            c = document.getElementById('content');
    
        var reader = new FileReader();
        reader.onloadend = function(e) {
            if(this.readyState == FileReader.DONE) {
                c.textContent = this.result;
                r.textContent = "Read bytes: " + (start + 1) + " - " + (end + 1) + " of " + file.size + " bytes";
            }
        };
    
        var blob;
        if(file.webkitSlice) {
            blob = file.webkitSlice(start, end + 1);
        } else if(file.mozSlice) {
            blob = file.mozSlice(start, end + 1);
        } else if(file.slice) {
            blob = file.slice(start, end + 1);
        }
    
        reader.readAsBinaryString(blob);
    };
    document.getElementById('file5').onchange = function() {
        readBlob(10, 100);
    }

    html代码如下:

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>分段读取文件title>
    head>
    <body>
        <input type="file" id="file5">
        <div id="range">div>
        <div id="content">div>
    div>
    body>
    html>
    <script type="text/javascript">
    (function() {
    function readBlob(start, end) {
        var files = document.getElementById('file5').files;
    
        if(!files.length) {
            alert('请选择文件');
            return false;
        }
    
        var file = files[0],
            start = parseInt(start, 10) || 0,
            end = parseInt(end, 10) || (file.size - 1);
    
        var r = document.getElementById('range'),
            c = document.getElementById('content');
    
        var reader = new FileReader();
        reader.onloadend = function(e) {
            if(this.readyState == FileReader.DONE) {
                c.textContent = this.result;
                r.textContent = "Read bytes: " + (start + 1) + " - " + (end + 1) + " of " + file.size + " bytes";
            }
        };
    
        var blob;
        if(file.webkitSlice) {
            blob = file.webkitSlice(start, end + 1);
        } else if(file.mozSlice) {
            blob = file.mozSlice(start, end + 1);
        } else if(file.slice) {
            blob = file.slice(start, end + 1);
        }
    
        reader.readAsBinaryString(blob);
    };
    document.getElementById('file5').onchange = function() {
        readBlob(10, 100);
    }
    })();
    script>

    本例使用了FileReader的onloadend事件来检测读取成功与否,如果用onloadend则必须检测一下FileReader的readyState,因为read abort时也会触发onloadend事件,如果我们采用onload,则可以不用检测readyState。

    分段读取进度

    那分段读取一个大文件时,如何监控整个文件的读取进度呢?
    这种情况下,因为我们调用了多次FileReader的文件读取方法,跟上文一次性把一个文件读到内存中的情况不大相同,不能用onprogress来监控。
    我们可以监听onload事件,每次onload代表每个片段读取完毕,我们只需要在onload中计算已读取的百分比就可以了!

    var bar2 = document.getElementById('progress-bar2');
    var progress2 = document.getElementById('progress2');
    var input6 = document.getElementById('file6');
    var block = 1 * 1024 * 1024; // 每次读取1M
    // 当前文件对象
    var file;
    // 当前已读取大小
    var fileLoaded;
    // 文件总大小
    var fileSize;
    
    // 每次读取一个block
    function readBlob2() {
        var blob;
        if(file.webkitSlice) {
            blob = file.webkitSlice(fileLoaded, fileLoaded + block + 1);
        } else if(file.mozSlice) {
            blob = file.mozSlice(fileLoaded, fileLoaded + block + 1);
        } else if(file.slice) {
            blob = file.slice(fileLoaded, fileLoaded + block + 1);
        } else {
            alert('不支持分段读取!');
            return false;
        }
        reader.readAsBinaryString(blob);
    }
    // 每个blob读取完毕时调用
    function loadHandler2(e) {
       fileLoaded += e.total;
       var percent = fileLoaded / fileSize;
       if(percent < 1)  {
           // 继续读取下一块
           readBlob2();
       } else {
           // 结束
           percent = 1;
       }
       percent = Math.ceil(percent * 100) + '%';
       progress2.innerHTML = percent;
       progress2.style.width = percent;
    }
    function fileSelect6(e) {
        file = this.files[0];
        if(!file) {
            alert('文件不能为空!');
            return false;
        }
        fileLoaded = 0;
        fileSize = file.size;
        bar2.style.display = 'block';
        // 开始读取
        readBlob2();
    }
    var reader = new FileReader();
    // 只需监听onload事件
    reader.onload = loadHandler2;
    input6.onchange = fileSelect6

    运行此示例:(提示:请选择一个1G以上文件)

    html源码:

     

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>分段读取文件title>
    head>
    <body>
        <p><input type="file" id="file6">p>
        <div id="progress-bar2" style="border:1px solid #333;padding:5px;color:#fff;display:none;">
            <div style="width:0%;height:100%;background:blue;white-space:nowrap;" id="progress2">div>
        div>
    body>
    html>
    
    <script type="text/javascript">
    (function() {
        var bar2 = document.getElementById('progress-bar2');
        var progress2 = document.getElementById('progress2');
        var input6 = document.getElementById('file6');
        var block = 1 * 1024 * 1024; // 1M
        var file;
        var fileLoaded;
        var fileSize;
    
        function readBlob2() {
            var blob;
            if(file.webkitSlice) {
                blob = file.webkitSlice(fileLoaded, fileLoaded + block + 1);
            } else if(file.mozSlice) {
                blob = file.mozSlice(fileLoaded, fileLoaded + block + 1);
            } else if(file.slice) {
                blob = file.slice(fileLoaded, fileLoaded + block + 1);
            } else {
                alert('不支持分段读取!');
                return false;
            }
            reader.readAsBinaryString(blob);
        }
        function loadHandler2(e) {
           fileLoaded += e.total;
           var percent = fileLoaded / fileSize;
           if(percent < 1)  {
               // 继续读取下一块
               readBlob2();
           } else {
               // 结束
               percent = 1;
           }
           percent = Math.ceil(percent * 100) + '%';
           progress2.innerHTML = percent;
           progress2.style.width = percent;
        }
        function fileSelect6(e) {
            file = this.files[0];
            if(!file) {
                alert('文件不能为空!');
                return false;
            }
            fileLoaded = 0;
            fileSize = file.size;
            bar2.style.display = 'block';
            readBlob2();
        }
        var reader = new FileReader();
        reader.onload = loadHandler2;
        input6.onchange = fileSelect6
        
    })();
    script>
    View Code

    注意事项
    在chrome浏览器上测试时,如果直接以file://xxx这种形式访问demo,会出现FileReader读取不到内容的情况,表现为FileReader的result为空或者FileReader根本就没有去读取文件内容,FileReader各个事件没有触发;
    这种情况我想应该是类似于chrome不允许添加本地cookie那样,chrome也不允许以file://xxx这种页面上的js代码访问文件内容;
    解决办法很简单,只需要将测试文件放到一个web服务器上,以http://xxx形式访问即可。

    参考网址:

    • http://www.html5rocks.com/en/tutorials/file/dndfiles/
    • http://hushicai.com/2014/03/29/html5-du-qu-ben-di-wen-jian.html

    你可能感兴趣的:(html5 文件系统File API)