下面要做一個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>ajax-upload</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>
<p>圖片預覽(如果上傳文件時圖片)</p>
<div class="img-preview">
</div>
</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>
<p id="time"></p>
</div>
</div>
</div>
</div>
<script>
(function () {
'use strict';
var file = document.querySelector('#file');
var upload = document.querySelector('#upload');
var progress = document.querySelector('#progress');
var time = document.querySelector('#time');
var imgPreview = document.querySelector('.img-preview');
var xhr = new XMLHttpRequest();
var loaded = 0, ot = 0, total = 0, oloaded = 0 ;//;
upload.addEventListener('click', uploadFile, false);
file.addEventListener('change', previewImage, false);
// 點擊上傳
function uploadFile(event) {
if(!file.files[0]) {
alert('請選擇文件')
return
}
if(file.files[0].size>10000000) {
alert('文件不得超過10M')
return
}
var formData = new FormData();
formData.append('test-upload', file.files[0]);
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.reload();
},1000)
}
}
// 進度條
function setProgress(event) {
// event.total是需要傳輸的總字節,event.loaded是已經傳輸的字節。如果event.lengthComputable不為真,則event.total等於0
if (event.lengthComputable) {//
loaded = event.loaded
total = event.total
var complete = (event.loaded / event.total * 100).toFixed(1);
progress.innerHTML = Math.round(complete) + "%";
progress.style.width = complete + '%';
}
var time = document.getElementById("time");
var nt = new Date().getTime();//獲取當前時間
var pertime = (nt-ot)/1000; //計算出上次調用該方法時到現在的時間差,單位為s
ot = new Date().getTime(); //重新賦值時間,用於下次計算
var perload = event.loaded - oloaded; //計算該分段上傳的文件大小,單位b
oloaded = event.loaded;//重新賦值已上傳文件大小,用以下次計算
//上傳速度計算
var speed = perload/pertime;//單位b/s
var bspeed = speed;
var units = 'b/s';//單位名稱
if(speed/1024>1){
speed = speed/1024;
units = 'k/s';
}
if(speed/1024>1){
speed = speed/1024;
units = 'M/s';
}
speed = speed.toFixed(1);
//剩余時間
var resttime = ((event.total-event.loaded)/bspeed).toFixed(1);
time.innerHTML = '傳輸速度:'+speed+units+',剩余時間:'+resttime+'s';
// if(bspeed==0) time.innerHTML = '上傳已取消';
}
// 圖片預覽
function previewImage(event) {
imgPreview.innerHTML = ''
// 每次重新選擇文件的時候,都會去除上次選擇產生的img標簽
var isImg = (event.target.files[0].type).indexOf('image/') > -1;
if(isImg) {
// 如果是圖片 就解析圖片預覽
var img = document.createElement('img')
imgPreview.appendChild(img)
var reader = new FileReader();
reader.onload = function (event) {
img.src = event.target.result;
img.width = '200'
};
reader.readAsDataURL(event.target.files[0]);
} else {
// imgPreview.appendChild('<img src=""/>')
// 可以為非圖片文件選擇一個默認的文件logo
}
}
//++++++++++++++++++++++++++++++++++++++++++++
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.兼容的ajax代碼段
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:個人服務器存儲較小,只是用來展示,后端是個小白,沒有做一些文件過濾和后端文件風險校驗,大神請繞行哈,還有請手下留情哈!
推薦文章:
