You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
这些方法在XHR对象中都存在同名版本,区别是后者是用于加载资源时,而前者用于资源上传时。其中onprogress 事件回调方法可用于跟踪资源上传的进度,它的event参数对象包含两个重要的属性loaded和total。分别代表当前已上传的字节数(number of bytes)和文件的总字节数。比如我们可以这样计算进度百分比:
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
var percentComplete = (event.loaded / event.total) * 100;
// 对进度进行处理
}
}
其中事件的lengthComputable属性代表文件总大小是否可知。如果 lengthComputable 属性的值是 false,那么意味着总字节数是未知并且 total 的值为零。
var fileInput = document.getElementById("myFile");
var files = fileInput.files;
var formData = new FormData();
for(var i = 0; i < files.length; i++) {
var file = files[i];
formData.append('files[]', file, file.name);
}
XMLHttpRequest.prototype.sendAsBinary = function(text){
var data = new ArrayBuffer(text.length);
var ui8a = new Uint8Array(data, 0);
for (var i = 0; i < text.length; i++){
ui8a[i] = (text.charCodeAt(i) & 0xff);
}
this.send(ui8a);
}
FileUpload 对象
在网页上传文件,最核心元素就是这个HTML DOM的FileUpload对象了
<input type="file">
就是他!其实在 HTML 文档中该标签每出现一次,一个 FileUpload 对象就会被创建。该标签包含一个按钮,用来打开文件选择对话框,以及一段文字显示选中的文件名或提示没有文件被选中。
把这个标签放在
标签内,设置form的action为服务器目标上传地址,并点击submit按钮或通过JS调用form的submit()方法就可以实现最简单的文件上传了。但是这样上传文件会有个问题,就是会打开一个新的网页,我们要的是页面无刷新上传。
更优雅的上传
现代网页通过什么来实现用户与服务器的无刷新交互?
——XMLHttpRequest
对,就是这个你很熟悉的家伙。如果你开发的产品支持的浏览器是现代浏览器,那么恭喜你,文件上传就是这么easy!特别强调强调现代浏览器是因为我们接下来讨论的XMLHttpRequest指的是XMLHttpRequest Level 2。
那什么是Level 1?为什么不行?因为它有如下限制:
而XMLHttpRequest Level 2针对这些缺陷做出了改进:
上面提到的FormData就是我们最常用的一种方式。通过在脚本里新建FormData对象,把File对象设置到表单项中,然后利用XMLHttpRequest异步上传到服务器:
FormData对象用以将数据编译成键值对,以便用XMLHttpRequest来发送数据。其主要用于发送表单数据,但亦可用于发送带键数据(keyed data),而独立于表单使用。如果表单enctype属性设为multipart/form-data ,则会使用表单的submit()方法来发送数据,从而,发送数据具有同样形式。
完成最基本的需求无法满足我们对用户体验的追求,所以我们还想要支持上传进度显示和上传图片预览。
上传进度
因为是XMLHttpRequest Level 2, 所以很容易就可以支持对上传进度的监听。细心地小伙伴会发现在chrome的developer tools的console里new一个XHR对象,调用点运算符就可以看到智能提示出来一个onprogress事件监听器,那是不是我们只要绑定XHR对象的progress事件就可以了呢?
很接近了,但是XHR对象的直属progress事件并不是用来监听上传资源的进度的。XHR对象还有一个属性upload, 它返回一个XMLHttpRequestUpload 对象,这个对象拥有下列下列方法:
这些方法在XHR对象中都存在同名版本,区别是后者是用于加载资源时,而前者用于资源上传时。其中onprogress 事件回调方法可用于跟踪资源上传的进度,它的event参数对象包含两个重要的属性loaded和total。分别代表当前已上传的字节数(number of bytes)和文件的总字节数。比如我们可以这样计算进度百分比:
其中事件的lengthComputable属性代表文件总大小是否可知。如果 lengthComputable 属性的值是 false,那么意味着总字节数是未知并且 total 的值为零。
如果是现代浏览器,可以直接配合HTML5提供的元素使用,方便快捷的显示进度条。
再说说我在测试这个progress事件时遇到的一个问题。一开始我设在onprogress事件回调里的断点总是只能走到一次,并且loaded值始终等于total。觉得有点诡异,改用console.log打印loaded值不见效,于是直接加大上传文件的大小到50MB,终于看到了5个不同的百分比值。
因为xhr.upload.onprogress在上传阶段(即xhr.send()之后,xhr.readystate=2之前)触发,每50ms触发一次。所以文件太小网络环境好的时候是直接到100%的。
图片预览
一般的图片预览方式是待文件上传成功后,后台返回上传文件的url,然后把预览图片的img元素的src指向该url。这其实达不到预览的效果和目的。
现代浏览器的图片预览:
这里我们使用FileReader来处理图片的异步加载。在创建新的FileReader对象之后,我们建立了onload函数,然后调用readAsDataURL()开始在后台进行读取操作。当图像文件加载后,转换成一个 data: URL,并传递到onload回调函数中设置给img的src。
多文件支持
FileUpload对象,它有一个multiple属性。
<input id="myFile" type="file" multiple>
这样就能在打开的文件选择对话框中选中多个文件了。然后你在代码里拿到的FileUpload对象的files属性就是一个选中的多文件的数组了。
FormData的append方法提供第三个可选参数用于指定文件名,这样就可以使用同一个表单项名,然后用文件名区分上传的多个文件。这样也方便前后台的循环操作。
二进制上传
有了FileReader,其实我们还有一种上传的途径,读取文件内容后直接以二进制格式上传。
不过chrome已经把XMLHttpRequest的sendAsBinary方法移除了。所以可能得自行实现一个
这段代码将字符串转成8位无符号整型,然后存放到一个8位无符号整型数组里面,再把整个数组发送出去。
拖拽的支持
利用HTML5的drag & drop事件,我们可以很快实现对拖拽的支持。首先我们可能需要确定一个允许拖放的区域,然后绑定相应的事件进行处理。看代码
这里可以把通过事件对象的dataTransfer拿到的files数组和之前相同处理,以实现预览上传等功能。有了这些事件回调,我们也可以在不同的事件给我们UI元素添加不同的class来实现更好交互效果。
最后来看看IE10以下的浏览器如何实现无刷新上传。
借用iframe
之前说了要实现文件上传使用FileUpload对象即可。这在低版本的IE里也是适用的。那我们为什么还要用iframe呢?
因为在现代浏览器中我们可以用XMLHttpRequest Level 2来支持二进制数据,异步文件上传,并且动态创建FormData。而低版本的IE里的XMLHttpRequest是Level 1。所以我们通过XHR异步向服务器发上传请求的路走不通了。只能老老实实的用form的submit。
而form的submit会导致页面的刷新。我们能不能让form的submit不刷新整个页面呢?答案就是利用iframe。把form的target指定到一个看不见的iframe,那么返回的数据就会被这个iframe接受,于是乎就只有这个iframe会刷新。而它又是看不见的,用户自然就感知不到了。
然后响应iframe的onload事件,获取response
iframe的实现大致如此,但是如果文件上传的地址与当前页面不在同一个域下就会出现跨域问题。导致iframe的onload回调里的访问服务返回的数据失败。
这时我们再祭出JSONP这把利剑,来解决跨域问题。首先在上传之前注册一个全局的函数,把函数名发给服务器。服务器需要配合在response里让浏览器直接调用这个函数。
综上所述,我们通过iframe也能实现一个无刷新的文件上传了。
转自:https://juejin.cn/post/6844903476804190221
The text was updated successfully, but these errors were encountered: