用javascript 上传文件

来源:互联网 发布:联合石油数据库官网 编辑:程序博客网 时间:2024/05/28 04:54
使用 javascript 来操作文件,是严格被禁止的,因为你不想一打开网页,硬盘灯就狂闪,然后把你硬盘的文件/列表都慢慢的上传上去,那么你就危险了。所以一般情况下,javascript 操作文件,都是在网页中提供文件上传控件。此时,你需要允许,才会使此网页获得相应的文件的信息。HTML5 以前的文件上传控件,都是以

<input type="file">
来进行的,此时,我们会得到关于此文件的一些信息,包括。

name – 文件名
size – 文件大小
type – 文件的 MIME 类型。
而此时你就可以随时访问这些文件的属性而不用直接去操作文件。那么就用正常的表单方式上传就可以了。

获得文件属性

在点击浏览添加完文件之后,就可以使用如下的 javascript 代码来获取相应的文件属性了。此处使用了添加事件(change)的方式,当然一旦选择了文件之后,这些属性随时都可以获得。

<input type="file" id="your-files" multiple>
<script>
var control = document.getElementById("your-files");
control.addEventListener("change", function(event) {
    // 当 control 改变的时候
    var i = 0,
        files = control.files,
        len = files.length;
    for (; i < len; i++) {
        console.log("Filename: " + files[i].name);
        console.log("Type: " + files[i].type);
        console.log("Size: " + files[i].size + " bytes");
    }
}, false);
</script>
当 your-files 这个对象发生改变的时候,就执行下面的代码。

拖动上传/Drag and drop

虽然之前的文件上传方式可以使用,但是总是不人性化,操作起来比较麻烦,而且还不强大。(其实我认为拖动上传也不人性化,最大化浏览器好好地,要上传了,先缩小浏览器窗口,然后在拖动?麻烦不!)
但是拖动上传总有他的优点呢。并且 html5 相对来说,给了上传文件更多的属性和事件。

例如,如下代码:

<div id="your-files">&lt;/div>
<script>
var target = document.getElementById("your-files");
 
target.addEventListener("dragover", function(event) {
    event.preventDefault();
}, false);
 
target.addEventListener("drop", function(event) {
 
    // 阻止浏览器默认动作
    event.preventDefault();
 
    var i = 0,
        files = event.dataTransfer.files,
        len = files.length;
 
    for (; i < len; i++) {
        console.log("Filename: " + files[i].name);
        console.log("Type: " + files[i].type);
        console.log("Size: " + files[i].size + " bytes");
    }
 
}, false);
</script>
当你将文件拖动到 your-files 区域的时候,就可以看到 控制台 里面输出文件的信息了。当然,此时还不能上传。

使用 Ajax 上传

获得此文件以及相应的信息之后,就可以开始上传了。当然这里使用 ajax 方式上传。使用了定义在 XMLHttpRequest Level 2 中的 FormData 对象,可以表示一个 HTML 表单,并且使用“属性:值” 这样的方式,使用 append() 方法来提交表单。


var form = new FormData();
form.append("name", "Nicholas");
FormData 对象的好处就是可以直接的获取文件内容。并且可以十分有效的模拟一个表单,而你只需要增加一些特别的信息,例如文件名等,其他的浏览器都会自己去做。


// 创建一个有多个数据的表单
var form = new FormData();
form.append("name", "Nicholas");
form.append("photo", control.files[0]);
 
// 通过 XHR 传送,没有传送 header!
var xhr = new XMLHttpRequest();
xhr.onload = function() {
    console.log("Upload complete.");
};
xhr.open("post", "/entrypoint", true);
xhr.send(form);
一旦通过 send() 方法提交表单之后,浏览器就会同时将 header 信息也提交给服务器。所以不用担心文件的编码问题。其实就像原始的表单提交一样,例如此处,对于 php 来说,就可以使用:


$_POST["name"];
$_POST["photo"];
来获取相应文件的信息了。不过目前只有现代浏览器才支持,例如 IE 要等到 10 才行。

下一节,虽然添加,上传文件很简单,这么点就弄完了,但是如何获取文件数据呢?这就是下节要讨论的了。


javascript 上传文件相关 (2)
上篇文章,简述了使用 javascript 对于文件上传的一些操作,例如,当用户想要上传,或者拖拽上传文件的时候。如何获得文件对象。那么,这篇文章,将简单的讲述一些读取文件数据的操作。

FileReader

FileReader 只有一个功能,那就是读取文件数据并且将其存储在 javascript 变量中,此 API 专门被设计成模拟(类似) XMLHttpRequest 操作,因为它们两个都是从外部资源加载数据(不包括浏览器)。并且此 API 被设计成异步的(asynchronously),所以不会引起浏览器的锁死。

FileReader 可以将数据读取成各种格式,而且在读取数据的时候,必须要求使用这几种格式,这几种格式如下,必须通过调用这几种格式才能读取数据。


readAsText() – 返回文本数据(text/plain)。
readAsBinaryString() – 返回此文件被编码的二进制数据 (已弃用– 使用 readAsArrayBuffer() 代替)。
readAsArrayBuffer() – 返回此文件的一个ArrayBuffer(二进制数据缓冲)(适合二进制数据,例如图像文件)数据。
readAsDataURL() – 返回此文件的 <a href="http://en.wikipedia.org/wiki/Data_URI_scheme" title="Data URI scheme" target="_blank">data URL</a>
没中方法都初始化一个文件读取动作,类似于 XHR 对象的 send() 方法初始化一个 http 请求。所以,必须对此绑定一些事件来监听文件读取的发生,例如 load 事件,表示文件读取完成,代码如下:


var reader = new FileReader();
reader.onload = function(event) {
    var contents = event.target.result;
    console.log("File contents: " + contents);
};//文件读取完成
 
reader.onerror = function(event) {
    console.error("File could not be read! Code " + event.target.error.code);
};//文件读取错误
 
reader.readAsText(file);//以文本方式读取
此代码主要是通过文本(text/plain)凡是来读取数据,并将其在 控制台(console) 显示,当文件被成功读取,onload 事件触发,当文件读取出错, onerror 事件触发,在事件内部,FileReader 的实例,应该使用 event.target 而不是直接使用保存 reader 的变量,读取文件成功时, result 属性保存文件内容,读取文件失败时, result 属性保存错误信息。

使用 data URIs 读取数据

稍微改变一下上面的代码,就可以实现使用 data URIs/data URLs 读取数据,尤其是从本地读取比较小的图片,读取并且在浏览器中显示,是非常便利的。


var reader = new FileReader();
reader.onload = function(event) {//成功读取
    var dataUri = event.target.result,//数据内容
        img = document.createElement("img");//创建一个 img 对象
 
    img.src = dataUri; //设置 img 对象的 src 属性值为 dataUri
    document.body.appendChild(img);//将 img 插入到 body 中
};
 
reader.onerror = function(event) {
    console.error("File could not be read! Code " + event.target.error.code);
};//读取文件错误是发生
 
reader.readAsDataURL(file);// 以 data url 方式读取数据
此代码将图片从本地以 data url 方式读取后,存入 img 对象的 src 属性中,然后直接在页面显示,如果可以,你可以直接将此图片放到页面上进行处理,不过要使用 <canvas> 才行。


var reader = new FileReader();
reader.onload = function(event) {
    var dataUri = event.target.result,
        context = document.getElementById("mycanvas").getContext("2d"),
    img = new Image();
    img.onload = function() {// 等待图像文件被加载完
        canvas.drawImage(img, 100, 100);
    };
    img.src = dataUri;
};
 
reader.onerror = function(event) {
    console.error("File could not be read! Code " + event.target.error.code);
};
此代码将图像文件加载完成之后,放到一个 canvas 画布中进行处理。

以 ArrayBuffers 方式读取

ArrayBuffer 最初是作为 WebGL 的一部分的,ArrayBuffer 代表一个可以存储任意任意大小的数字的一个有限的区域,此种方式读取的数据,必须使用特殊的方式访问,例如 Int8Array ,将此底层的数据视为 8 位的符号整数。或者 Float32Array 将其视为 32 位的浮点数,这些都被叫做类型化数组/格式化数组,都被强制的使用某种数据格式来使用,而不像普通格式的数据那样。

ArrayBuffer 主要用来处理二进制数据文件,已能够对数据具有更细粒度的控制,并且远远超出此篇文章解释的范围。反正在使用的时候,只要记住,你可以很简单的将一个文件读取为二进制缓冲,并且可以简单地将此数据通过 XHR 的 send() 方法发送给后端服务器(当然了,你必须从服务器上接受此二进制缓冲,并且重建此文件),但是浏览器还必须完整的支持 XMLHttpRequest Level 2,不过可惜的是,只有 IE10 以上的现代浏览器才支持。

预告
FileReader 的使用很简单,但是,如果你知道如何使用 XMLHttpRequest 的话,没有理由你不会用这种方式读取文件,下节,将介绍更多的关于 FileReader 的事件,和更多可能的错误。


javascript 上传文件相关 (3)
FileReader 对象被用来,通过浏览器来读取文件数据,在上篇文章中,讨论了可以使用 FileReader 用多种格式来方便的读取文件数据,在很多方面,FileReader 和 XMLHttpRequest 非常类似。

Progress events (进度事件)

实际上,Progress 事件是一个单独定义的很普通的规范,这些事件被定义来大概的显示数据传输的进度,例如,当从服务器传送数据开始发生的时候,同样的可以是FileReader 对象开始从磁盘读取数据事件发生的时候。

总共有六个事件:


loadstart – 当加载数据开始的时候,此事件通常都是使用在整个过程的最开始。
progress – 当数据读取的时候,可以获得数据读取进度。
error – 当数据加载失败的时候。
abort – 当数据加载被 abort() 方法取消的时候,(在 FileReader 和 XMLHttpRequest 都可用)。
load – 当所有数据都被成功加载的时候。
loadend – 当数据完成传输的时候,一般情况下,都是在出错,取消,或者成功加载后。所以其不区分成功与否。只要过程完成就好。
error 和 load 已经在上篇文章中讨论过了,这里将讨论其他的一些事件。

跟踪进度

当想要知道文件的读取进度的时候,就应该使用 progress 事件,此事件对象,包含了三个属性,来表示文件开始传输之后的状态。


lengthComputable ─ true或者false,表示请求数据的大小是否已知。
loaded ─ 目前接收到的字节数。
total ─ 整个请求中期望传输的字节数。
注意:当lengthComputable为false时,total属性值为0。loadstart事件只会被激发一次。在loadstart之后,进度事件可能被激发0次或多次。
这些数据的作用是可以使 进度条(progress Html5)使用 progress 事件的信息进行进度展示。例如使用 Html5 的 progress 标签来展示数据读取进度。

var reader = new FileReader(),
     progressNode = document.getElementById("my-progress");
 
reader.onprogress = function(event) {
    if (event.lengthComputable) {
        progressNode.max = event.total;
        progressNode.value = event.loaded;
    }
};
 
reader.onloadend = function(event) {
    var contents = event.target.result,
        error = event.target.error;
 
    if (error != null) {
        console.error("File could not be read! Code " + error.code);
    } else {
        progressNode.max = 1;
        progressNode.value = 1;
        console.log("Contents: " + contents);
    }
};
 
reader.readAsText(file);
相同的例子就是 Gmail 使用的拖拽上传,当把文件拖拽到 Gmail 里之后,就会立即出现进度条,显示上传进度。

处理错误

虽然是从本地上传文件,但有时候还是会失败。File API 定义了四种错误类型。


NotFoundError – 找不到文件。
SecurityError – 文件本身或者读取存在危险,一般情况下,如果读取此文件是危险的或者已经读取了太多次此文件,就会出现该错误。
NotReadableError – 文件存在,但是没有权限读取。
EncodingError – 编码错误,只要是作为 data URI 读取的时候,读取结果的长度超过了浏览器支持的最大长度。
当读取文件时有错误发生时,FileReader 对象的错误属性,就会成为上面提到的一个错误类型的一个实例。在规范中,浏览器都会用一个代码表明错误的属性和值。

FileError.NOT_FOUND_ERR -- NotFoundError.
FileError.SECURITY_ERR -- SecurityError.
FileError.NOT_READABLE_ERR -- NotReadableError.
FileError.ENCODING_ERR for -- EncodingError .
FileError.ABORT_ERR -- 当 abort() 方法调用的时候。
可以通过下面的代码测试不同的错误:

var reader = new FileReader();
 
reader.onloadend = function(event) {
    var contents = event.target.result,
        error = event.target.error;
 
    if (error != null) {
        switch (error.code) {
            case error.ENCODING_ERR:
                console.error("Encoding error!");
                break;
 
            case error.NOT_FOUND_ERR:
                console.error("File not found!");
                break;
 
            case error.NOT_READABLE_ERR:
                console.error("File could not be read!");
                break;
 
            case error.SECURITY_ERR:
                console.error("Security issue with file!");
                break;
 
            default:
                console.error("I have no idea what's wrong!");
        }
    } else {
        progressNode.max = 1;
        progressNode.value = 1;
        console.log("Contents: " + contents);
    }
};
 
reader.readAsText(file);
下节

FileReader 对象有很多的功能的对象的集合,大部分都和 XMLHttpRequest 类似。通过这三篇文章,应该能够通过 javascript 读取文件并把文件发送到服务器了。 但是 File API 系统,要比上面这三篇文章讲到的多得多,下篇文章将会有更给力的功能来进行讨论。

javascript 上传文件相关 (4) URL 对象
会使用传统的方式上传图片,也能够用 js 从本地读取图片,现在讨论一下一种更简单的方法来上传文件。那就是 URL 对象 。

什么是 URL 对象(object url)

URL 对象就是一个指向磁盘上文件的 url,例如,想要在浏览器中显示一个文件,而这个文件存在与用户的电脑上,通常的办法是先上传到服务器,然后在现实,但这里却不需要服务器的干预,只需要加载图片即可。上一节的讨论可以讲文件加载进一个 file 对象。将图像文件数据读取为 data URI 然后将此 data URI 加入一个 img 对象的属性中即可,但是既然图像已经存在于硬盘上,为什么还要将它读取为其他格式,然后再来使用呢?为何要多此一举呢?如果使用 URL 对象的话,那么就可以直接将硬盘里的文件付给 img 对象了。

如何工作?

File API 对全局的 URL 对象,定义了两个方法,一个是 createObjectURL() ,接受一个指向文件的参数,然后返回 URL 对象 ,这就使得浏览器能够创建和管理一个指向本地文的 URL ;另一个方法是 revokeObjectURL() ,使得浏览器可以销毁传递给它的 URL 对象,以释放内存,并且页面一旦关闭,所有的 url 对象都会被销毁。不过这样做的好处就是不需要他们的时候,浏览器可以及时释放内存。

示例:

那该怎么做呢?首先给用户一个选择文件的方式,然后将其引用到一个变量 file。然后可以使用下面的代码:

var URL = window.URL || window.webkitURL,
    imageUrl,
    image;
 
if (URL) {
    imageUrl = URL.createObjectURL(file);
    image = document.createElement("img");
 
    image.onload = function() {
        URL.revokeObjectURL(imageUrl);
    };
 
    image.src = imageUrl;
    document.body.appendChild(image);
}
示例首先创建了一个本地 URL 变量(假设此 URL 变量正常可用),接着代码直接将本地的文件创建了一个 URL 对象然后将其存储在 imageUrl 中,然后后创建一个 img 对象,给 img 对象一个事件,等 img 对象加载完成之后,销毁 imageUrl ,然后将此 imageUrl 付给 img 对象的 src 属性,再将其插入到页面,此时,就可以看到图像在页面上显示了。

为什么在图像加载完成之后就销毁次数据呢,因为此数据在这里只会被使用一次,所以手动的释放它以节省内存占用,但是如果还有其他用处的话,就不应该手动销毁它。

安全问题和其他注意事项

初一看,此功能有点恐怖,因为竟然可以直接加载用户的磁盘文件。当然了,这样有一些安全隐患,不过 URL 本身并没有多大的问题,因为它都是动态分配,并且在其他计算机上无法访问。

对于不同的源 File API 是不允许被使用的,当 URL 对象被创建的时候,它就被绑定到此 javascript 运行的页面,所以不可能从 www.yimity.com 访问到 post.yimity.com 页面的 URL 对象,但是两个页面都来源于 www.yimity.com ,并且一个页面是另一个页面的 iframe 的话,那就可以访问到了。

URL 对象的储存时间很短,只在文档创建他们的期间存在,一旦页面被关闭,则他们都会被销毁。所以他们不会被存储在客户端。因为页面一关闭,他们都是无用的数据了。

在 GET 请求中可以使用 URL 对象,就和正常的脚本,图像一样。但是在 post 请求中确实不可用的,例如给 form post 属性。

下一节

下一节将会有更多的关于如何处理文件数据的方式。


javascript 上传文件相关 (5) Blob 对象
这一系列文章都讲述的是关于使用 JavaScript 操作文件相关的知识,其中最重要的是 File 对象,而实际上 file 对象只是 blob 对象的一个更具体的版本,blob 存储着大量的二进制数据,并且 blob 的 size 和 type 属性,都会被 file 对象所继承。

所以,可以说,在大多数情况下,blob 对象和 file 对象可以用在同一个地方,例如,可以使用 FileReader 借口从 blob 读取数据,也可以使用 URL.createObjectURL() 从 blob 创建一个新的 URL 对象。

Slicing (分割)

通过 Blob 对象可以做的一件有趣的事情就是可以创建一个子 Blob 对象,其实就是可以将其分割(file 对象也可以)。由于每个 Blob 对象都是通过指针指向数据的而不是指向数据本身,因此可以快速的创建指向其他子部分的新的 Blob 对象,这里就需要使用 slice() 方法了。是不是和 JavaScript 的 slice() 方法很象,其实差不多。

此方法接受三个参数,起始偏移量,结束偏移量,还有可选的 mime 类型。如果 mime 类型,没有设置,那么新的 Blob 对象的 mime 类型和父级一样。

不过目前浏览器实现此方法还没有统一,火狐使用的是 mozSlice() ,Chrome 使用的是 webkitSlice() ,其他浏览器则正常的方式 slice() 。重写的兼容各个浏览器的例子如下:


function sliceBlob(blob, start, end, type) {
 
    type = type || blob.type;
 
    if (blob.mozSlice) {
        return blob.mozSlice(start, end, type);
    } else if (blob.webkitSlice) {
        return blob.webkitSlice(start, end type);
    } else {
        throw new Error("This doesn't work!");
    }
}
当要上传大文件的时候,此方法非常有用,可以将大文件分割分段,然后各自上传,因为分割之后的 Blob 对象和原始的是独立存在的。例如,Flickr 的工程师就使用此方法将照片中的需要使用的 exif 信息截取出来,而不是等到其传到服务器上之后,才处理的,并且一旦选择要上传照片,则同时传输文件数据和 Exif 数据,这样几乎就可以在上传照片的时候同时显示照片的信息了。

使用旧方法创建 Blob 对象

当 File 对象开始出现在浏览器中的时候,开发者们意识到 Blob 对象太强大了,都想可以在用户不干预的情况下,创建 Blob 对象,毕竟任何数据都可以用 Blob 对象表示,不用非要和文件产生关系。通过 BlobBuilder 创建一个包含有特定数据的 Blob 对象,然后浏览器快速响应即可。(不过目前其还不统一,Firefox:MozBlobBuilder, Internet Explorer 10:MSBlobBuilder,Chrome:WebKitBlobBuilder)。

例子:

var builder = new BlobBuilder();
builder.append("Hello world!");
var blob = builder.getBlob("text/plain");
BlobBuilder() 创建一个新实例,并且使用一个 append() 方法,将字符串(或者 ArrayBuffer 或者 Blob,此处用 string 举例)插入,一旦数据插入成功,就可以使用 getBlob() 方法设置一个 mime 。

并且 BlobBuilder() 还可以处理动态数据,例如 worker 中的数据等,这里就不翻译了。

使用新方法创建 Blob 对象

因为开发者一直想要能够直接创建 Blob 对象,因此浏览器实现了 BlobBuilder(); Blob 作为构造函数而存在,而且,此构造函数也已经被作为标准了,其也是今后创建 Blob 对象的方式。

Blob()--构造函数,接受两个参数,第一个为一个数据序列,可以是任意格式的值,例如,任意数量的字符串,Blobs 以及 ArrayBuffers。第二个参数,是一个包含了两个属性的对象,其两个属性分别是:

type -- MIME 的类型。

endings -- 决定 append() 的数据格式,(数据中的 \n 如何被转换)可以取值为 "transparent" 或者 "native"(t* 的话不变,n* 的话按操作系统转换;t* 为默认) 。

例子:

1
var blob = new Blob(["Hello world!"], { type: "text/plain" });
可以看到,此方法比 BlobBuilder() 简单多了。

但是,此 Blob 构造函数还没有被一些浏览器实现,目前只有某些版本的 Chrome 和 Firefox 实现了。而且剩余的浏览器也没有确定什么时候实现,但是其现在作为 File API 的一部分,将来应该会被统一实现的。
原创粉丝点击