上一篇《H5拖放+FormData接口+NodeJS,完整异步文件上传(一)》,我们走通了拖放文件上传的整个流程,但离实际使用场景还有差距。这篇,我们来添加几个实际使用场景必要的功能,向实际使用再走一步。
添加功能
显示待上传文件列表;
支持移除待上传文件;
使用upload.onprogress显示上传进度;
支持中断上传;
upload.progress
XMLHttpRequest.upload方法返回一个 XMLHttpRequestUpload对象,用来表示上传的进度。这个对象是不透明的,但是作为一个XMLHttpRequestEventTarget,可以通过对其绑定事件来追踪它的进度。onprogres监听数据传输进行中(通过监听这个事件,可获得上传进度)。摘自MDN
实现思路
拖放文件到上传区域时,将文件保存在一个文件数组中,添加并显示文件相关信息和移除按钮,点击移除按钮删除文件数组中对应的文件元素,点击上传按钮,遍历文件数组,开始上传待上传文件,此时点击移除按钮则中止文件上传。
代码
//相关样式.drop-area{ margin:auto; width: 500px; height: 500px; border:1px pink dashed; } .close-btn{ cursor: pointer; } .close-btn:after{ float: right; content: 'X'; color: red; }#fileList{ width: 95%; } .process-bar{ position: relative; margin: 0 10px 0 10px; width: 198px; height: 18px; display: none; text-align: center; line-height: 20px; color: #6dbfff; border-radius: 3px; border: 1px solid #1483d8; background: #fff;} .success .process-bar, .uploading .process-bar{ display: inline-block; } .process-bar .process-text{ position: relative; z-index: 1; } .process-bar .process-rate{ position: absolute; width: 0; height: 100%; left: 0; top: 0; border-radius: 3px; background: #1483d8;} .file-list .success .process-text, .file-list .success .close-btn:after, .file-list .error .process-text, .file-list .error .close-btn:after{ display: none; } .success .process-bar :after, .error .process-bar :after{ content:'success'; position: absolute; margin: auto; left: 0; right: 0; z-index: 2; } .error .process-bar:after{ content: "error"; color: red; }
<!--HTML--><div class="drop-area" ondrop="drop_hander(event)" ondragover = "dragover_hander(event)"></div><!--监听拖放DOM--><div id="fileList" class="file-list"></div><!--显示待上传文件列表--><button id="submit">上传</button><!--上传按钮-->
//Javascript代码let filesSet = []; //文件保存数组let fileList = document.getElementById('fileList'); //获取显示文件列表DOM/** * 创建一个新的空白的文档片段frag * 用于附加多个待上传文件的DOM,可减小回流 * https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createDocumentFragment */let frag = document.createDocumentFragment();let barDom = createProccessBar(); //创建进度条DOMlet submit = document.getElementById("submit") //获取提交按钮/* 拖动到放置区域时 */function dragover_hander (event) { /* 必须同时阻止dragover和drop的默认事件 否则会响应浏览器默认行为 浏览器器能显示的文件会直接显示,例如html文件、图片文件 浏览器器不能显示的文件会出现文件下载弹窗 */ event.preventDefault(); }/*拖放完成事件*/function drop_hander (event) { event.preventDefault(); //阻止默认事件 var files = event.dataTransfer.files; //通过dataTransfer对象获取文件对象数组 for(let file of files) { //遍历文件对象数组 //创建文件信息显示DOM,并保存在file对象的element属性中,用于后续操作 file.element = createFileDom(file, filesSet.length) //复制进度条DOM,并保存在file对象的processBar属性中,用于后续操作 file.processBar = filesSet.length?barDom.cloneNode(true):barDom; //将进度条添加至文件信息显示DOM中 file.element.appendChild(file.processBar); //file文件对象添加到文件保存数组 filesSet.push(file); //创建文件信息显示DOM添加至文档片段frag frag.appendChild(file.element); } //将文件列表添加至显示文件列表的div fileList.appendChild(frag) }/** * 创建文件信息显示DOM * file 文件对象,用于获取文件信息 * index 文件对象在数组中的索引,用于删除 */function createFileDom (file, index) { let p = document.createElement('p'); //file.name属性可以获得文件名称 //有兴趣的童鞋,可以使用for...in循环查看一下file对象的其它属性值 let text = document.createTextNode(file.name); let span = document.createElement("span"); span.setAttribute('index', index); //索引添加至按钮的index属性 span.className = 'close-btn'; p.appendChild(text); p.appendChild(span); return p; //返回文件信息显示DOM}/** * 创建进度条DOM, * 进度条DOM结构固定,可使用clone(true)进行复制 * @return {[type]} [description] */function createProccessBar() { let bar = document.createElement("span"); let rate = document.createElement("span"); let text = document.createElement("span"); bar.className = "process-bar"; rate.className = "process-rate"; text.className = "process-text"; text.innerText="0%"; bar.appendChild(text); bar.appendChild(rate); return bar; } //通过事件代理,监听移除或中止上传fileList.addEventListener('click', (evt)=>{ let index = evt.target.getAttribute('index'); //获得index属性值 if (index) { //存在index属性值,表示点击了删除按钮 if (filesSet[index].unloading && filesSet[index].req) { //文件已经上传中 filesSet[index].req.abort(); //中止上传 filesSet[index].unloading = false; //将上传中的状态设置为false } else { //未开始上传 filesSet[index].element.remove(); //移除DOM filesSet[index].element = null; //释放对DOM的引用 filesSet[index].processBar = null;//释放对DOM的引用 delete filesSet[index];//删除文件数组中对应的元素 } } }) submit.addEventListener('click',function(){// 为上传按钮绑定事件 //这里使用for...in循环,正好可以避免对数组稀疏元素的遍历 for(let key in filesSet){ //如果正在上传中或已经上传完成,不再上传 if (filesSet[key].unloading || filesSet[key].uploaded) continue; filesSet[key].unloading = true; //标记开始上传 //创建一个文件上传的Promise,并设置成功及失败的回调 initUpload(filesSet[key]).then(file => { //上传成功 file.element.className = "success"; //UI显示成功信息 file.uploaded = true; //标记上传成功 }).catch((file, err) => { file.element.className = "error"; //UI显示失败信息 //取消开始上传标记,点击上传按钮将尝试再次上传 filesSet[key].unloading = false; }) } })/** * 返回一个文件上传的Promise实例 * @param {[type]} file 要上传的文件 */function initUpload(file){ return new Promise((res, rej) => { let formData = new FormData();//声明一个FormData实例 let req = new XMLHttpRequest();//创建XHR实例 let reta = file.processBar.querySelector('.process-rate');//获得进度条DOM let text = file.processBar.querySelector('.process-text');//获得进度文本DOM let pre;//保存上传百分比 //监听数据传输进行中 req.upload.onprogress =function(data){ pre = (data.loaded/data.total*100).toFixed(2);//计算百分比 reta.style.width = pre +'%';//修改进度条 text.innerText = pre +'%' ;//修改进度条文本 } //监听请求完成 req.onreadystatechange = function () { if (req.readyState !==4 ) return ; if (req.status === 200 ){ //完成,执行成功回调 res(file, req) } else { //失败,执行失败回调 rej(file, req) } } formData.append('file',file); //使用append方法添加文件到file键 req.open('POST', '/process_post'); //初始化请求 req.send(formData); //发送请求 file.req = req; //保存req对象,用于中止请求 //形如显示上传进度 file.element.className = "uploading" }) }
到这里代码就结束了,完整代码可以查看Github。因为是本地上传,在测试的时候可以使用大一些的文件,或者限制一下上传。
结束语
这些新的API,使得文件拖放上传变得简单起来。可惜低版本的IE并不支持,听说低版本的IE可以使用Falsh来进行文件上传,具体是如何实现的,要不我们下篇再来探讨一下。
作者:快减肥成功的胖纸
链接:https://www.jianshu.com/p/7a77665acb18
点击查看更多内容
为 TA 点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦