下面要做一個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(圖檔如下)
- 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-uploadnpm 官網上的資料顯示居然已經有一定的下載下傳量,實在很驚訝,不過也很歡迎大家學習使用,有bug,及時告知于我
https://www.npmjs.com/package/vue-ajax-upload如有錯誤,敬請指出!
這裡提供了一個demo示範如下, ps:個人伺服器存儲較小,隻是用來展示,後端是個小白,沒有做一些檔案過濾和後端檔案風險校驗,大神請繞行哈,還有請手下留情哈!
戳我