这是《大胖小课》栏目的专题一《说说文件上传那些事儿》的第4节-《玩玩多文件配多进度上传》
专题已经更新章节:
《大胖 • 小课》- 我是这样理解文件上传原理的
《大胖 • 小课》- 写一个文件上传接口
《大胖 • 小课》- 不用 js 实现文件无刷新上传
既然要说多文件配多进度上传,那就要看看单个进度是如何实现的。
多文件,单进度
借助XMLHttpRequest2
的能力,实现多个文件或者一个文件的上传进度条的显示。
DEMO
说明
- 页面内增加一个用于显示进度的标签
div.progress
js
内处理增加进度处理的监听函数xhr.upload.onprogress
event.lengthComputable
这是一个状态,表示发送的长度有了变化,可计算event.loaded
表示发送了多少字节event.total
表示文件总大小- 根据
event.loaded
和event.total
计算进度,渲染div.progress
PS 特别提醒
xhr.upload.onprogress
要写在xhr.send
方法前面,否则event.lengthComputable
状态不会改变,只有在最后一次才能获得,也就是100%
的时候.
HTML
<div> 选择文件(可多选): <input type="file" id="f1" multiple/><br/><br/> <div id="progress"> <span class="red"></span> </div> <button type="button" id="btn-submit">上 传</button> </div>
JS
<script> function submitUpload() { var progressSpan = document.getElementById('progress').firstElementChild; var fileList = document.getElementById('f1').files; progressSpan.style.width='0'; progressSpan.classList.remove('green'); if(!fileList.length){ alert('请选择文件'); return; } var fd = new FormData(); //构造FormData对象 fd.append('title', document.getElementById('title').value); for(var i =0;i<fileList.length;i++){ fd.append('f1', fileList[i]);//支持多文件上传 } var xhr = new XMLHttpRequest(); //创建对象 xhr.open('POST', 'http://10.70.65.235:8100/', true); xhr.onreadystatechange = function () { console.log('state change', xhr.readyState); if (xhr.readyState == 4) { var obj = JSON.parse(xhr.responseText); //返回值 console.log(obj); if(obj.fileUrl.length){ //alert('上传成功'); } } } xhr.onprogress=updateProgress; xhr.upload.onprogress = updateProgress; function updateProgress(event) { console.log(event); if (event.lengthComputable) { var completedPercent = (event.loaded / event.total * 100).toFixed(2); progressSpan.style.width= completedPercent+'%'; progressSpan.innerHTML=completedPercent+'%'; if(completedPercent>90){//进度条变色 progressSpan.classList.add('green'); } console.log('已上传',completedPercent); } } //注意 send 一定要写在最下面,否则 onprogress 只会执行最后一次 也就是100%的时候 xhr.send(fd);//发送时 Content-Type默认就是: multipart/form-data; } //绑定提交事件 document.getElementById('btn-submit').addEventListener('click',submitUpload); </script>
CODE
https://github.com/Bigerfe/fe-learn-code/tree/master/src/upfiles-demo/demo5
多文件上传+预览+取消
上一个栗子的多文件上传只有一个进度条,有些需求可能会不大一样,需要观察到每个文件的上传进度,并且可以终止上传。
DEMO
说明
- 为了预览的需要,我们这里选择上传图片文件,其他类型的也一样,只是预览不方便
- 页面内增加一个多图预览的容器
div.img-box
- 根据选择的文件信息动态创建所属的预览区域和进度条以及取消按钮
- 为取消按钮绑定事件,调用
xhr.abort();
终止上传 - 使用
window.URL.createObjectURL
预览图片,在图片加载成功后需要清除使用的内存window.URL.revokeObjectURL(this.src);
HTML
<div> 选择文件(可多选): <div class="addfile">添加文件 <input type="file" id="f1" multiple /> </div> <div class="img-box"></div> <button type="button" id="btn-submit">上 传</button> </div>
JS
<script> //更改网络 为慢3g,就可以比较明显的看到进度条了 var fileMaxCount=6; var imgBox =document.getElementsByClassName('img-box')[0]; var willUploadFile=[];//保存待上传的文件以及相关附属信息 document.getElementById('f1').addEventListener('change',function (e) { var fileList = document.getElementById('f1').files; if (willUploadFile.length > fileMaxCount || fileList.length>fileMaxCount || (willUploadFile.length+ fileList.length>fileMaxCount)) { alert('最多只能上传' + fileMaxCount + '张图'); return; } for (var i = 0; i < fileList.length; i++) { var f = fileList[i];//先预览图片 var img = document.createElement('img'); var item = document.createElement('div'); var progress = document.createElement('div'); progress.className='progress'; progress.innerHTML = '<span class="red"></span><button type="button">Abort</button>'; item.className='item'; img.src = window.URL.createObjectURL(f); img.onload = function () { //显示要是否这块儿内存 window.URL.revokeObjectURL(this.src); } item.appendChild(img); item.appendChild(progress); imgBox.appendChild(item); willUploadFile.push({ file:f, item, progress }); } }); function xhrSend({file, progress}) { var progressSpan = progress.firstElementChild; var btnCancel = progress.getElementsByTagName('button')[0]; var abortFn=function(){ if(xhr && xhr.readyState!==4){ //取消上传 xhr.abort(); } } btnCancel.removeEventListener('click',abortFn); btnCancel.addEventListener('click',abortFn); progressSpan.style.width='0'; progressSpan.classList.remove('green'); var fd = new FormData(); //构造FormData对象 fd.append('f1',file); var xhr = new XMLHttpRequest(); //创建对象 xhr.open('POST', 'http://localhost:8100/', true); xhr.onreadystatechange = function () { console.log('state change', xhr.readyState); //调用 abort 后,state 立即变成了4,并不会变成0 //增加自定义属性 xhr.uploaded if (xhr.readyState == 4 && xhr.uploaded) { var obj = JSON.parse(xhr.responseText); //返回值 console.log(obj); if(obj.fileUrl.length){ //alert('上传成功'); } } } xhr.onprogress=updateProgress; xhr.upload.onprogress = updateProgress; function updateProgress(event) { if (event.lengthComputable) { var completedPercent = (event.loaded / event.total * 100).toFixed(2); progressSpan.style.width= completedPercent+'%'; progressSpan.innerHTML=completedPercent+'%'; if(completedPercent>90){//进度条变色 progressSpan.classList.add('green'); } if(completedPercent>=100){ xhr.uploaded=true; } console.log('已上传',completedPercent); } } //注意 send 一定要写在最下面,否则 onprogress 只会执行最后一次 也就是100%的时候 xhr.send(fd);//发送时 Content-Type默认就是: multipart/form-data; return xhr; } //文件上传 function submitUpload(willFiles) { if(!willFiles.length){ return; } //遍历文件信息进行上传 willFiles.forEach(function (item) { xhrSend({ file:item.file, progress:item.progress }); }); } //绑定提交事件 document.getElementById('btn-submit').addEventListener('click',function () { submitUpload(willUploadFile); }); </script>
问题1
这里没有做上传的并发控制,可以通过控制同时可上传文件的个数(这里控制为最多6个)或者上传的时候做好并发处理,也就是同时只能上传 X 个文件。
问题2
在测试过程中,取消请求的方法xhr.abort()
调用后,xhr.readyState
会立即变为4
,而不是0
,所以这里需要做容错处理。
MDN 上说是0.
如果大家有不同的理解,欢迎留言。