JavaScript进阶学习(三)—— 基于html5 File API的文件操做

文章来源:小青年原创
发布时间:2016-08-16
关键词:blob,File,FileReader,DataURI,URL
转载需标注本文原始地址: http://zhaomenghuan.github.io...javascript

写在前面

这段时间一直有朋友在问文件上传下载的事,搜一下论坛发现相关的问题很多,可是不够系统,本着为人民服务的态度本文试着将一些问题整理一下,争取用初学者能够更明确的去处理相关的问题。文件上传是开发中绕不过的一个坎儿,对于不少没有经验的人来讲,简直懵逼,目前我所知道的上传方式有下面这几种:css

  • 传统flash上传
  • 隐藏iframe框上传
  • 表单数据提交
  • HTML5的新工具——File API

本文限于篇幅先介绍最后一种使用html5 File API进行文件上传的相关细节。html

历史上,JavaScript没法处理二进制数据。若是必定要处理的话,只能使用charCodeAt()方法,一个个字节地从文字编码转成二进制数据,还有一种办法是将二进制数据转成Base64编码,再进行处理。这两种方法不只速度慢,并且容易出错。ECMAScript 5引入了Blob对象,容许直接操做二进制数据。Blob对象是一个表明二进制数据的基本对象,在它的基础上,又衍生出一系列相关的API,用来操做文件。前端

File API

File 接口提供了文件的信息,以及文件内容的存取方法。html5

File对象能够用来获取某个文件的信息,还能够用来读取这个文件的内容。一般状况下,File对象是来自用户在一个<input>元素上选择文件后返回的FileList对象,也能够是来自由拖放操做生成的 DataTransfer对象.java

经过input file标签选择文件

默认的input file标签比较难看,须要本身改造,通常无非是将input file设置宽高,而后使用overflow: hidden;将多余的部分隐藏,在上面再盖一个美化的按钮或者提示语,以下图:git

浏览器原生的效果:github

clipboard.png

通过美化的效果:
clipboard.pngweb

代码以下:chrome

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style type="text/css">
            *{
                margin: 0px;
                padding: 0px;
            }
            .filePicker{
                width: 160px;
                height: 44px;
                line-height: 44px;
                text-align: center;
                color: #fff;
                background: #00b7ee;        
            }
            .filePicker input[type="file"] {
                position: relative;
                top: -44px;
                left: 0px;
                width: 160px;
                height: 44px;
                opacity: 0;
                cursor: pointer;
                overflow: hidden;
                z-index: 0;
            }
            
            .container{
                width: 160px;
                margin: 30px auto;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <input type="file" name="" id="" value="" />
        </div>    
        <div class="container">
            <div class="filePicker">
                <label>点击选择文件</label>
                <input id="fileInput" type="file" name="file" multiple="multiple" accept="image/*">
            </div>
        </div>
    </body>
</html>

咱们能够经过给input file标签设置accept属性进行文件选择过滤,该属性的值必须为一个逗号分割的列表,包含了多个惟一的内容类型声明:

  • 以 STOP 字符 (U+002E) 开始的文件扩展名。(例如:".jpg,.png,.doc")
  • 一个有效的 MIME 类型,但没有扩展名
  • audio/* 表示音频文件 HTML5
  • video/* 表示视频文件 HTML5
  • image/* 表示图片文件

设置multiple属性能够进行设置为多选。

设置capture属性能够进行设置打开摄像拍照或者录像。

Capture Image: 
<input type="file" accept="image/*" capture="camera"> 
Capture Audio: 
<input type="file" accept="audio/*" capture="microphone"> 
Capture Video: 
<input type="file" accept="video/*" capture="camcorder">

multiple属性和capture属性不能同时生效。

经过File API,咱们能够在用户选取一个或者多个文件以后,访问到表明了所选文件的一个或多个File对象,这些对象被包含在一个FileList对象中。全部type属性(attribute)为file的<input>元素都有一个files属性,用来存储用户所选择的文件。files有一个length属性和item方法,咱们能够经过files[index]或者files.item(index)获取咱们选择的file对象。能够经过change事件监听input file输入完成事件:

var fileInput = document.getElementById("fileInput");
fileInput.addEventListener('change', function(event) {
    var file = fileInput.files[0];
    // 或file = fileInput.files.item(0);
    console.log(file);
}, false);

File API提供File对象,它是FileList对象的成员,包含了文件的一些元信息,好比文件名、上次改动时间、文件大小和文件类型。下图能够File对象的属性:

clipboard.png

  • lastModifiedDate:文件对象最后修改的日期
  • name:文件名,只读字符串,不包含任何路径信息.
  • size:文件大小,单位为字节,只读的64位整数.
  • type:MIME类型,只读字符串,若是类型未知,则返回空字符串.

例如:咱们能够根据size换算出咱们习惯的文件大小表达方式:

/**
 * 读文件大小
 * @param {Object} file
 */
function readFileSize(file){
    var size = file.size / 1024;
    var aMultiples = ["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
    
    var fileSizeString = '';
    for(var i = 0; size > 1; size /= 1024, i++) {
       fileSizeString = size.toFixed(2) + " " + aMultiples[i];
    }
    return fileSizeString;
}

有时候咱们但愿限制用户上传的文件大小,能够经过这个方法先作判断。同时咱们能够经过type属性判断用户的文件类型,可是这种方法不可靠,由于用户能够经过改变后缀名实现。

不少新手企图经过input file标签得到文件完整路径,因为浏览器安全机制,这个是不被容许的,可是有时候咱们但愿选择完图片预览一下图片,这个时候咱们就能够用FileReader API实现。

经过拖放操做选择文件

预览效果

clipboard.png

代码实现

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style type="text/css">
            .dropbox{
                width: 300px;
                height: 300px;
                margin: 20px;
                border: 3px dashed #e6e6e6;
            }
            .area{
                margin: 100px auto;
                width: 100px;
                height: 100px;
                background-repeat: no-repeat;
                background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFgAAABLCAIAAAB7tddWAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1Q0VBNzA0MjEyMDUxMUUzODk2Q0JFM0Q1RjE4QkExQyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1Q0VBNzA0MzEyMDUxMUUzODk2Q0JFM0Q1RjE4QkExQyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjAzNDA2MkY1MTIwMzExRTM4OTZDQkUzRDVGMThCQTFDIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjAzNDA2MkY2MTIwMzExRTM4OTZDQkUzRDVGMThCQTFDIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+K6izdgAAAvpJREFUeNrsnFmPqkAQhWmX667gEp9c/v+/MkSDG+4LrvdcSYgRbw/0ALZQ9WBUJOn+uqvqHGCG3e93hUJRUoSAQBAIAkEgCASBIBAE4neRicEcII51Xb/dbnjPGOt0OqlUKok7ApN3jIKwY6DUIBAEgkAQCALho/X47TeXy8U0TcuyrtdrZKPs9/v2m8FgINYgf9QX/gTV+Xw2DCNKBJKmxmKxsAVc0kEcDgfyGq8CNp/Pa5qWy+WiHG6v13v7/XPt6Ha7Al5D3HQ1Go1sNkvtU8lkMsmtESSoCASBSFqEW/DQ0tbr9W63O51OKK6FQkFV1XQ6nSwQ0OOTyQSvjknZbDaA0mq1QCQafSFFasxmM4eCE1Do0+lUQrcSFggsPhzq20NgsVqtkpIax+MxMs+C/aXruvMxaonND75J9W5hUWWxuYAVdRfTg8EplUphGJywQPAFuBd5Dlhw/aDwwgVtCDgCdzph1QisG+dosVjkn44WYxjGC4XnvBuNRtvt9gtA2Hv47SGsZKVS4ef8eDzG4vMVCrpSgEU3xPbZbDar1erLl1AQ7XabU8xAAXvB3XffBnIHwkR2QcUYq9fr5XIZOxkTg6BEkeNLKdQF7AWPFBy1AoUmu8RG/HmE91nxM+J/ORIr07VcLvf7feCt+stAQGIBRNJtOJolDAhdj/hXGj5+u+TzIKAF+MbkK00XFta2BhDRUE0/9gv8Elogbu4TBW8+nyPhHSeuaVqtVuNQC6TzyQUCXsg0TbfyAxfIKogr9ynP1GJyYQZ57qbg7AuIRfclKZwSlDqWBQSmxM9zFALYh+fFBwJJSkNgqeFxSrAPw+EQ9QJew7Is2Sj8FgSW2nu1gylYPkKRMsRTA+4IcjA2fxsnDkLOq/IfACFP54uP1yAQBIJAEIh4gWCPkHk+GJ7AjU/fICJ+qlIghEfoDwQMtRjvyLYDRih4rsDT+bBM9tP5kuhrzN++e6SqqvCdYUb/SIO6BoEgEASCQBAIAkEgCEQg8VeAAQAB1bbO2qoeewAAAABJRU5ErkJggg==");
            }
            
            #preview img{
                width: 100px;
                height: 100px;
            }
        </style>
    </head>
    <body>
        <div id="dropbox" class="dropbox">
            <div class="area"></div>
        </div>
        <div id="preview"></div>
        
        <script type="text/javascript">
            var dropbox = document.getElementById("dropbox");
            var preview = document.getElementById("preview");
            
            dropbox.addEventListener("dragenter", function(e){
                e.stopPropagation();
                  e.preventDefault();
            }, false);
            
            dropbox.addEventListener("dragover", function(e){
                e.stopPropagation();
                  e.preventDefault();
            }, false);
            
            dropbox.addEventListener("drop", function(e){
                e.stopPropagation();
                  e.preventDefault();
            
                  var dt = e.dataTransfer;
                  var files = dt.files;
                  
                  for (var i = 0; i < files.length; i++) {
                    var file = files[i];
                    var imageType = /^image\//;

                    if ( !imageType.test(file.type) ) {
                          continue;
                    }
                    
                    // 填充选择的图片到展现区
                    var img = document.createElement("img");
                    img.classList.add("obj");
                    img.file = file;
                    preview.appendChild(img);
                    
                    // 读取File对象中的内容
                    var reader = new FileReader();
                    reader.onload = (function(aImg) { 
                      return function(e) { 
                        aImg.src = e.target.result; 
                      }; 
                    })(img);
                    reader.readAsDataURL(file);
                }
            }, false);
        </script>
    </body>
</html>

在这个例子中,ID 为 dropbox 的元素所在的区域是咱们的拖放目的区域。咱们须要在该元素上绑定 dragenter,dragover,和drop 事件。咱们必须阻止dragenter和dragover事件的默认行为,这样才能触发 drop 事件。咱们从drop 事件对象中获取到dataTransfer对象,这个对象包含Filelist对象。

FileReader API

使用FileReader对象,web应用程序能够异步的读取存储在用户计算机上的文件(或者原始数据缓冲)内容,可使用File对象或者Blob对象来指定所要处理的文件或数据.其中File对象能够是来自用户在一个<input>元素上选择文件后返回的FileList对象,也能够来自拖放操做生成的 DataTransfer对象,还能够是来自在一个HTMLCanvasElement上执行mozGetAsFile()方法后的返回结果。

DataURI对象

在上面经过拖放操做选择文件的例子中,咱们使用了"data:image/png;base64,xxxxxxxxxxxxx"这种形式的字符串做为背景,而不是图片,选择的图片展现也是使用这种形式。这种字符串叫作DataURI对象,容许将一个小文件进行编码后嵌入到另一个文档里,格式为:

data:[<MIME type>][;charset=<charset>][;base64],<encoded data>

总体能够视为三部分,即声明:参数+数据,逗号左边的是各类参数,右边的是数据。

URL是uniform resource locator的缩写,在web中的每个可访问资源都有一个URL地址,例如图片,HTML文件,js文件以及style sheet文件,咱们能够经过这个地址去download这个资源。其实URL是URI的子集,URI是uniform resource identifier的缩写。URI是用于获取资源,包括其附加的信息的一种协议。附加信息多是地址,也可能不是地址,若是是地址,那么这时URI就变成URL了。注意的是data URI不是URL,由于它并不包含资源的公共地址。

咱们能够经过FileReader 的readAsDataURL方法得到:

var reader = new FileReader();
reader.onload = function() {
    console.log(this.result);
}
reader.readAsDataURL(file);

有时候咱们须要将DataURI对象转blob对象:

/**
 * dataURI 转 blob
 * @param {Object} dataURI
 */
function dataURItoBlob(dataURI) {
    var arr = dataURI.split(','), mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
    while(n--){
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {type:mime});
}

URL对象

咱们除了可使用base64字符串做为内容的DataURI将一个文件嵌入到另一个文档里,还可使用URL对象。URL对象用于生成指向File对象或Blob对象的URL。

window.URL.createObjectURL

静态方法会建立一个 DOMString,它的 URL 表示参数中的对象。这个 URL 的生命周期和建立它的窗口中的 document 绑定。这个新的URL 对象表示着指定的 File 对象或者 Blob 对象。

var objecturl =  window.URL.createObjectURL(file);

window.URL.revokeObjectURL

静态方法用来释放一个以前经过调用 window.URL.createObjectURL() 建立的已经存在的 URL 对象。当你结束使用某个 URL 对象时,应该经过调用这个方法来让浏览器知道再也不须要保持这个文件的引用了。

window.URL.revokeObjectURL(objecturl)

例如:使用对象URL来显示图片:

window.URL = window.URL || window.webkitURL;

var img = document.createElement("img");
img.src = window.URL.createObjectURL(blob);
img.height = 60;
img.onload = function(e) {
    window.URL.revokeObjectURL(this.src);
}
document.body.appendChild(img);

FileReader API详解

状态常量

  • EMPTY:值为0,尚未加载任何数据;
  • LOADING:值为1,数据正在被加载;
  • DONE:值为2,已完成所有的读取请求。

属性

  • error:在读取文件时发生的错误, 只读;
  • readyState:代表FileReader对象的当前状态,值为State constants中的一个,只读;
  • result:取到的文件内容,这个属性只在读取操做完成以后才有效,而且数据的格式取决于读取操做是由哪一个方法发起的,只读。

方法

  • abort():停止该读取操做.在返回时,readyState属性的值为DONE.
  • readAsArrayBuffer():开始读取指定的Blob对象或File对象中的内容. 当读取操做完成时,readyState属性的值会成为DONE,若是设置了onloadend事件处理程序,则调用之.同时,result属性中将包含一个ArrayBuffer对象以表示所读取文件的内容.
  • readAsBinaryString():开始读取指定的Blob对象或File对象中的内容. 当读取操做完成时,readyState属性的值会成为DONE,若是设置了onloadend事件处理程序,则调用之.同时,result属性中将包含所读取文件的原始二进制数据.
  • readAsDataURL():开始读取指定的Blob对象或File对象中的内容. 当读取操做完成时,readyState属性的值会成为DONE,若是设置了onloadend事件处理程序,则调用之.同时,result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容.
  • readAsText():开始读取指定的Blob对象或File对象中的内容. 当读取操做完成时,readyState属性的值会成为DONE,若是设置了onloadend事件处理程序,则调用之.同时,result属性中将包含一个字符串以表示所读取的文件内容.

事件处理

  • onabort:当读取操做被停止时调用.
  • onerror:当读取操做发生错误时调用.
  • onload:当读取操做成功完成时调用.
  • onloadend:当读取操做完成时调用,不论是成功仍是失败.该处理程序在onload或者onerror以后调用.
  • onloadstart:当读取操做将要开始以前调用.
  • onprogress:在读取数据过程当中周期性调用.

上传实例:以二进制流上传文件

var fileInput = document.getElementById("fileInput");
fileInput.addEventListener('change', function(event) {
    var file = fileInput.files[0];
    if (file) {
        var reader = new FileReader();  
        var xhr = new XMLHttpRequest();
        xhr.onprogress=function(e){
            var percentage = Math.round((e.loaded * 100) / e.total);
            console.log("percentage:"+percentage);
        }
        xhr.onload=function(e){
            console.log("percentage:100");
        }
        xhr.open("POST", "这里填写服务器地址");  
        reader.onload = function(evt) {
            xhr.send(evt.target.result);
        };
        reader.readAsBinaryString(file);
    }
});

blob 二进制大对象

BLOB (binary large object),二进制大对象,是一个能够存储二进制文件的容器。

建立Blob对象的方法有几种,能够调用Blob构造函数,还可使用一个已有Blob对象上的slice()方法切出另外一个Blob对象,还能够调用canvas对象上的toBlob方法。

第一次见到这个词是半年以前,那个时候竟然没有听过blob,而后上网查了一下,巴拉巴拉一大堆,当时是不理解的。

看了前面的一系列API和对象,或许不少同窗开始晕了,可是在一开始说到的blob对象,咱们一直没有提到,若是本文不说起,显然是不合理的。毕竟做为File对象的爸爸,blob劳苦功高。上述的FileReader对象也能够操做blob对象。

Blob对象有两个只读属性:

  • size:二进制数据的大小,单位为字节。
  • type:二进制数据的MIME类型,所有为小写,若是类型未知,则该值为空字符串。
    在Ajax操做中,若是xhr.responseType设为blob,接收的就是二进制数据。

Blob 构造函数生成blob对象

Blob构造函数,接受两个参数。第一个参数是一个包含实际数据的数组,第二个参数是数据的类型,这两个参数都不是必需的。数组元素能够是任意多个的ArrayBuffer,ArrayBufferView (typed array), Blob,或者 DOMString对象。
例如:

var arr = ['<h1>hello world</h1>'];
var blob = new Blob(arr, { "type" : "text/xml" }); // the blob
console.log(blob);

效果以下:

clipboard.png

用JS在浏览器中建立下载文件

前端不少项目中,都有文件下载的需求,特别是JS生成文件内容,而后让浏览器执行下载操做(例如在线图片编辑、在线代码编辑、iPresst等)但受限于浏览器,不少状况下咱们都只能给出个连接,让用户点击打开-》另存为。以下面这个连接:

<a href="file.js">file.js</a>

用户点击这个连接的时候,浏览器会打开并显示连接指向的文件内容,显然,这并无实现咱们的需求。HTML5中给a标签增长了一个download属性,只要有这个属性,点击这个连接时浏览器就不在打开连接指向的文件,而是改成下载(目前只有chrome、firefox和opera支持)。下载时会直接使用连接的名字来做为文件名,可是是能够改的,只要给download加上想要的文件名便可,如:download="not-a-file.js"。可是这样还不够,以上的方法只适合用在文件是在服务器上的状况。若是在浏览器端js生成的内容,想让浏览器进行下载要如何办到呢?DataURI能够实现这个效果,可是DataURI的文件类型被限制了,咱们这里能够变通一下实现blob对象。

<a id="aLink">下载</a>
<script type="text/javascript">
    function downloadFile (el, fileName, content) {
        var aLink = document.querySelector(el);
        var blob = new Blob([content]);                
        aLink.download = fileName;
        aLink.href = URL.createObjectURL(blob);
    }
    document.querySelector('#aLink').addEventListener('click',function () {
        downloadFile('#aLink', 'hello.txt', '<h1>hello world</h1>');
    })
</script>

Blob对象的slice方法生成blob对象

Blob对象的slice方法,将二进制数据按照字节分块,返回一个新的Blob对象。

var newBlob = oldBlob.slice(startingByte, endindByte);

下面是一个使用XMLHttpRequest对象,将大文件分割上传的例子。

function upload(blobOrFile) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };
  xhr.send(blobOrFile);
}

document.querySelector('input[type="file"]').addEventListener('change', function(e) {
  var blob = this.files[0];

  const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes.
  const SIZE = blob.size;

  var start = 0;
  var end = BYTES_PER_CHUNK;

  while(start < SIZE) {
    upload(blob.slice(start, end));

    start = end;
    end = start + BYTES_PER_CHUNK;
  }
}, false);

参考文档

在web应用中使用文件
DataURI详解
文件和二进制数据的操做
理解DOMString、Document、FormData、Blob、File、ArrayBuffer数据类型
用JS在浏览器中建立下载文件

写文章不容易,也许写这些代码就几分钟的事,写一篇你们好接受的文章或许须要几天的酝酿,而后加上几天的码字,累并快乐着。若是文章对您有帮助请我喝杯咖啡吧!

clipboard.png

相关文章
相关标签/搜索