下面要做一个ajax上传文件显示进度的操作:
这里先上代码:
1、前端代码
upload.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.css" rel="stylesheet"> <style> .progress-area { padding: 20px; } </style> </head> <body> <div class=""> <p>文件上传</p> <div> <input type="file" name="file" id="file" /> <div class="img-preview"> <p>图片预览(如果上传文件时图片)</p> <img id="image" src="" width="200" /> </div> <button id="upload">上传</button> <button id="btn">终止上传</button> <div class="progress-area"> 进度 <div class="progress"> <div class="progress-bar" id="progress" role="progressbar" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100">0%</div> </div> </div> </div> </div> <script> (function () { 'use strict'; var file = document.querySelector('#file'); var upload = document.querySelector('#upload'); var progress = document.querySelector('#progress'); var image = document.querySelector('#image'); var xhr = new XMLHttpRequest(); var loaded = 0; var total = 0; upload.addEventListener('click', uploadFile, false); file.addEventListener('change', previewImage, false); // 点击上传 function uploadFile(event) { if(!file.files[0]) { alert('请选择文件') return } var formData = new FormData(); // 利用这个来提交xhr,具体的可看下面的mdn 介绍 // https://developer.mozilla.org/zh-CN/docs/Web/API/FormData/Using_FormData_Objects formData.append('test-upload', file.files[0]); // 将input 选择出来的文件调用append()方法来添加字段 xhr.onload = uploadSuccess; // 上传方法 xhr.upload.onprogress = setProgress; // 上传过程方法 xhr.open('post', '/upload', true); // 打开请求方式和接口 xhr.send(formData); // 发送数据 } // 成功上传 function uploadSuccess(event) { if (xhr.readyState === 4 && xhr.status === 200) { // 判断接口调用成功的重要标识 setTimeout(()=> { alert('上传成功') // window.location.href="/" },1000) } } // 进度条 function setProgress(event) { console.log(event) if (event.lengthComputable) { // 先判断这个上传进程是否有这个文件长度的判断 loaded = event.loaded // 已经加载的字节 total = event.total // 上传伊始时候计算的总字节数 var complete = Number.parseInt(event.loaded / event.total * 100); // 取百分比 progress.style.width = complete + '%'; progress.innerHTML = complete + '%'; // css 偷了懒,用了bootstrap4的css } } // 图片预览 function previewImage(event) { var reader = new FileReader(); // h5 的新属性 reader.onload = function (event) { image.src = event.target.result; // 这里是用图片base64的字符串将图片显示出来,用作图片上传预览 }; reader.readAsDataURL(event.target.files[0]); } //++++++++++++++++++++++++++++++++++++++++++++ xhr.onloadstart = function(){ console.log("上传开始"); } btn.onclick = function(){ xhr.abort(); console.log("上传被终止."); progress.style.width = 0 + '%'; progress.innerHTML = 0 + '%'; // 这里调用取消上传的代码 }; xhr.ontimeout = function(){ console.log('上传超时.'); } // xhr.timeout = 50000; // 默认为0 没有时间限制 // xhr.onabort = function(){ // console.log("The transfer has been canceled by the user."); // } xhr.onerror = function(){ console.log("上传错误,可能是断网了,也可能是断请求服务了."); // 这里存在异步传输问题 return } xhr.onloadend = function(){ console.log("请求结束"); // 发送上传的请求,至于有没有上传成功,不清楚,可能失败 成功,这里只是请求结束了 } // +++++++++++++++++++++++++++++++++++++++++++ })(); </script> </body> </html>
2、后端接口(nodejs)
app.js
const express = require('express'); const upload = require('multer')({ dest: 'uploads/' }); const path = require('path'); const fs = require('fs'); const port = 8088; let app = express(); app.set('port', port); // index.html, index.js放在static文件夹中 app.use(express.static(path.join(__dirname, 'static'))); // app.get('/', (req, res) => { // res.redirect('upload2.html'); // }); // 路由/ajax-upload 就回渲染 upload.html 页面 app.get('/ajax-upload', function(req, res){ res.sendFile('upload.html', { root: __dirname }); }); app.post('/upload', upload.single('test-upload'), (req, res) => { // 没有附带文件 if (!req.file) { res.json({ ok: false }); return; } // 重命名文件 let oldPath = path.join(__dirname, req.file.path); let newPath = path.join(__dirname, 'uploads/' + req.file.originalname); fs.rename(oldPath, newPath, (err) => { if (err) { res.json({ ok: false }); console.log(err); } else { res.json({ ok: true }); } }); }); // 这里还没有做上传取消 删除文件的操作 app.get('/abort', upload.single('test-upload'), (req, res)=>{ console.log(req, 'abort') // 删除刚才上传的文件 }) app.listen(port, () => { console.log("[Server] localhost:" + port); });
注:接口使用
- 环境:nodejs
- 项目结构
- static(图片如下)
- app.js
- upload.html
- uploads
- static(图片如下)
- 框架包express: npm install express –save
- 上传包multer :npm install multer –save
- 启动: node app.js
以上代码可以直接复制运行:
3、解释
3.1.前端
上传简述: 前端的选择文件,掉后端上传接口,利用ajax技术将文件上传的服务器。
前端要知道后端是否上传成功或者状态,就得需要后端返回给我们的状态,最简单的就是上传成功或者失败,再者需要知道进度的就要利用xhr 的进程方法了。
- ajax 的原理就是利用浏览器的XMLHttRequest 这个对象,因为IE浏览器不是这对象,如果要兼容的话 ,可以封装一个XHR 对象,代码可以看后面的附属部分:
- XMLHttRequest 对象有多个方法,监控ajax 上传的进度主要利用这些属性,来达到我们的目标
- 下面是部分属性(方法)介绍,这里是MDN的介绍 xhr
-
- onloadstart 获取开始
- onprogress 数据传输进行中
- onabort 获取操作终止
- onerror 获取失败
- onload 获取成功
- ontimeout 获取操作在用户规定的时间内未完成
- onloadend 获取完成(不论成功与否)
* 对xhr.upload.onprogress的解释
这里监控进度主要看这里的属性值的变化,如下图
具体的代码片段解释可以查看我上面的代码注释
3.2.后端
这里使用的nodejs的一个接口,之前我的用的是将前端预览图片的base64的字符串传到后端,然后后端解析这个base64的字符串,生成图片,保存在磁盘中,但是会出现问题,进度不太好显示,
所以查了相关资料,借用了某位大神的写法,直接上传文 件;还有一个就是multer包,利用这个进行上传。
这里项目也是一个后端渲染的方式,将页面渲染好,然后发送给前端。
4、附属代码:
4.1.兼容的XMLHttpRequest代码段
function XMLHttpRequest(){ if (typeof XMLHttpRequest != "undefined"){ return new XMLHttpRequest(); } else if (typeof ActiveXObject != "undefined"){ if (typeof arguments.callee.activeXString != "string"){ var versions = [ "MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"], i, len; for (i=0,len=versions.length; i < len; i++){ try { new ActiveXObject(versions[i]); arguments.callee.activeXString = versions[i]; break; } catch (ex){ //跳过 } } } return new ActiveXObject(arguments.callee.activeXString); } else { throw new Error("No XHR object available."); } }
4.2.nodejs 处理base64字符串
node包:formidable
imgBase64Arr: 前端传值过来的 base64字符串的多张图片的数组
for(var i = 0;i<imgBase64Arr.length;i++) { var imgname = util.randomStr(); // 随机字符串的方法 imgname = 'assets/img/'+ imgname + '.png'; var base64 = imgBase64Arr[i].replace(/^data:image\/\w+;base64,/, ""); //去掉图片base64码前面部分data:image/png;base64 var dataBuffer = new Buffer(base64, 'base64'); //把base64码转成buffer对象, console.log('dataBuffer是否是Buffer对象:'+Buffer.isBuffer(dataBuffer)); fs.writeFile(imgname,dataBuffer,function(err){//用fs写入文件 if(err){ console.log(err); }else{ console.log('图片上传成功!'); } }) newimgarr.push(imgname.replace("assets","")); }
github地址:https://github.com/adouwt/ajax-upload
前端上传这块已经封装了一个基于vue的插件,
GitHub地址:https://github.com/adouwt/vue-upload
npm 官网上的数据显示居然已经有一定的下载量,实在很惊讶,不过也很欢迎大家学习使用,有bug,及时告知于我
https://www.npmjs.com/package/vue-ajax-upload
如有错误,敬请指出!
这里提供了一个demo演示如下, ps:个人服务器存储较小,只是用来展示,后端是个小白,没有做一些文件过滤和后端文件风险校验,大神请绕行哈,还有请手下留情哈!
戳我