前言: 之前我總以為瀏覽器上傳文件就一種方式——表單(表單包括 HTML 的 Form 表單,和虛擬表單 FormData 兩種)。現(xiàn)在我又接觸了一種文件上傳方法,那就是通過 HTML5 的 FileReader 來完成文件上傳项秉。
重點(diǎn)來了:
- 瀏覽器獲取用戶電腦本地的文件诬垂,是通過
<input type="file" />
標(biāo)簽來完成劲室。 - 獲取的內(nèi)容上傳到服務(wù)器,可以通過表單來完成剥纷,也可以通過 FileReader 來完成痹籍。
接下來呢铆,我們就看看如何利用 HTML5 的 FileReader 結(jié)合 NodeJS 實(shí)現(xiàn)文件上傳晦鞋。
一、認(rèn)識 FileReader
FileReader 直譯為文件讀取棺克,那肯定和 File
對象有關(guān)系悠垛,File
繼承于 Blob
,所以 FileReader 和 Blob
也有關(guān)系娜谊。此時你再去摟一遍文檔确买,FileReader-MDN,你就會發(fā)現(xiàn) FileReader API 的主要功能就是:
FileReader 可以把 File 或 blob 對象轉(zhuǎn)化為另一種格式(例如base64)纱皆,轉(zhuǎn)化為另一種格式后常用于圖片預(yù)覽和文件上傳湾趾。
FileReader 的生命周期
FileReader 附帶有 6 個主要事件:
- loadstart:當(dāng)我們開始加載文件時觸發(fā)。
- progress:當(dāng)在內(nèi)存中讀取 Blob 時觸發(fā)派草。
- abort:讀取文件被打斷時觸發(fā)搀缠。
- error:發(fā)生錯誤時觸發(fā)。
- load:讀取成功時觸發(fā)近迁。
- loadend:無論讀取文件成功還是失敗艺普,最后都會觸發(fā)。
FileReader 方法
常見的四個方法:
-
readAsArrayBuffer(file)
:讀取文件或 Blob 作為數(shù)組緩沖區(qū)鉴竭。常用于大文件上傳歧譬。 -
readAsDataURL(file)
:這將返回一個 URL,它是 Base64 編碼的搏存,常用于小文件上傳和圖片預(yù)覽瑰步。 -
readAsBinaryString(file)
:以二進(jìn)制字符串讀取文件,不常用璧眠,我也沒用過缩焦。 -
readAsText(file, format)
:將文件讀取為 USVString(幾乎像一個字符串),并且可以指定可選格式蛆橡。不常用舌界,我也沒用過。
二泰演、FileReader 圖片預(yù)覽
FileReader 通過 readAsDataURL 方法能把文件對象轉(zhuǎn)化成 base64 格式的 URL 呻拌,這個 URL 可以通過 img 標(biāo)簽被直接呈現(xiàn)在頁面上。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>圖片預(yù)覽</title>
<style>
body {
width: 20%;
margin: 100px auto;
}
</style>
</head>
<body>
<input type="file" onchange="handleFileChange(this)" />
<br />
<br />
<script>
function handleFileChange(instance) {
const file = instance.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (e) => {
const base64URL = e.target.result;
const img = new Image();
img.alt = img.title = file.name;
img.src = base64URL;
document.body.appendChild(img);
};
}
</script>
</body>
</html>
演示效果:
除了這種方法可以做到圖片預(yù)覽睦焕,還有一種方法也能做圖片預(yù)覽藐握,而且實(shí)現(xiàn)更加簡單靴拱,那就是 URL.createObjectURL
,它會把文件對象變成 blob 對象猾普,然后直接被 img 標(biāo)簽讀取袜炕,我們來看下怎么實(shí)現(xiàn)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>圖片預(yù)覽</title>
<style>
body {
width: 20%;
margin: 100px auto;
}
</style>
</head>
<body>
<input type="file" onchange="handleFileChange(this)" />
<br />
<br />
<script>
let imgUrl;
function handleFileChange(instance) {
const file = instance.files[0];
imgUrl = globalThis.URL.createObjectURL(file);
const img = new Image();
img.alt = img.title = file.name;
img.src = imgUrl;
document.body.appendChild(img);
}
// 解除 imgUrl
globalThis.addEventListener("beforeunload", (event) => {
imgUrl && globalThis.URL.revokeObjectURL(imgUrl);
event.preventDefault();
event.returnValue = "哈哈哈";
});
</script>
</body>
</html>
看下演示效果:
三初家、FileReader 文件上傳
在寫前端代碼之前偎窘,先來寫下后端,后端這里使用 NodeJS + Express 來實(shí)現(xiàn)溜在,我們來看下代碼實(shí)現(xiàn):
const bodyParser = require("body-parser");
const path = require("path");
const express = require("express");
const app = express();
const fs = require("fs");
// 解析有效路徑
function resolvePath (dir) {
return path.join(__dirname, dir);
};
// 靜態(tài)文件
app.use(express.static(resolvePath("/public")));
// 固定寫法陌知,處理 POST 請求
// https://expressjs.com/en/4x/api.html#req.body
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// 一個上傳的接口
app.post("/upload", function (req, res) {
const { fileName, fileData } = req.body;
// base64 => buffer
const chunk = Buffer.from(fileData, "base64");
const imgUrl = resolvePath(`public/${fileName}`);
// buffer 寫入文件
fs.writeFileSync(imgUrl, chunk);
// 返回結(jié)果
res.json({
code: "0",
message: "success",
imgUrl: `http://localhost:48488/${fileName}`
});
});
// 啟動服務(wù)
const port = 48488;
app.listen(port, function () {
console.log(`listen port ${port}`);
});
安裝對應(yīng)的依賴,使用 Node 命令直接啟動服務(wù)掖肋。接下里看前端部分仆葡,把文件上傳完成之后,再重新在服務(wù)器上讀取剛剛重新上傳的文件地址志笼,返回給前端展示沿盅。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FileReader實(shí)現(xiàn)文件上傳</title>
<style>
body {
width: 20%;
margin: 100px auto;
}
</style>
</head>
<body>
<input type="file" onchange="handleFileChange(this)" />
<br />
<br />
<script src="./axios.min.js"></script>
<script>
function handleFileChange(instance) {
const file = instance.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (e) => {
const base64URL = e.target.result;
const fileRes = axios.post("/upload", { fileData: base64URL, fileName: file.name });
fileRes.then((res) => {
const img = new Image();
img.alt = img.title = file.name;
img.style.height = "150px";
img.src = base64URL;
document.body.appendChild(img);
})
};
}
</script>
</body>
</html>
查看演示效果:
看了演示效果,我們發(fā)現(xiàn)代碼運(yùn)行的毫無問題纫溃,通過 FileReader 完成文件上傳功能已經(jīng)實(shí)現(xiàn)腰涧,不過可能這時候你會問了,為什么你演示的時候會有 413 錯誤呢皇耗?
哈哈哈南窗,這個,其實(shí)是 body-parser 設(shè)置的郎楼,我們看下 body-parser-limit 關(guān)于當(dāng)我們上傳字符串的說明万伤,說明顯示,請求體默認(rèn)被限制為了
100kb
大小呜袁。解決辦法敌买,就是擴(kuò)下容,比如擴(kuò)容到 50M阶界,代碼為app.use(bodyParser.json({ limit: "50mb" }));
這個時候虹钮,我來問你個問題,你知道為什么文件上傳很少用 FileReader 的原因嗎膘融?
答:base64 是一種編碼方式芙粱,文件經(jīng)過 base64 編碼之后,體積會變大氧映,所以不常用 base64 作為文件上傳的方式春畔。參見:Encoded size increase
最后
有點(diǎn)遺憾的是通過 FileReader 讀取文件轉(zhuǎn)成 ArrayBuffer 上傳,沒研究成功,這方便資料太少了律姨,我看也沒人用這種方式振峻,就算了吧。上傳文件還是老老實(shí)實(shí)的用 FormData 好了择份。
當(dāng)前時間 Thursday, January 21, 2021 11:17:54