js文件上傳原理(form表單 ,FormData + XHR2 + FileReader + canvas)
- form表單上傳
- FormData + XHR2 + FileReader + canvas 無刷新本地預覽壓縮上傳實例
目前實現上傳的方式
瀏覽器小於等於IE9(低版本瀏覽器)使用下面的方式實現的
- flash實現(主流插件的方式,本文不涉及)
form + iframe(項目中很少用到,本文不涉及)
form表單提交的方式是所有瀏覽器都支持的,借助iframe是為了實現不刷新界面上傳
主流瀏覽器 + IE10+ 則是通過以下方式實現的上傳
FormData + XHR2 + FileReader + canvas
FormData介紹
FormData接口提供了一種輕松構造一組表示表單字段及其值的鍵/值對的方法,然後可以使用XMLHttpRequest.send()方法輕松地發送這些值。如果將編碼類型設置為“multipart/form-data”,則使用與表單相同的格式。
常用方法:
FormData.append(0; // 添加鍵值對
FormData.delete(); // 刪除鍵值對
FormData.entries(); // 返回允許遍歷此對象中包含的所有鍵/值對的叠代器
...具體還有很多方法可以參考網站:FormData
XMLHttpRequest簡介
使用XMLHttpRequest (XHR)對象可以與服務器交互。您可以從URL獲取數據,而無需讓整個的頁面刷新。這使得Web頁面可以只更新頁面的局部,而不影響用戶的操作。XMLHttpRequest在 Ajax 編程中被大量使用。
常用屬性和方法
const xhr = new XMLHttpRequest() // 常用屬性 xhr.onreadystatechange // 當readyState屬性發生變化時調用的函數 xhr. readyState // 存有 XMLHttpRequest 的狀態。從 0 到 4 發生變化。 0: 請求未初始化 1: 服務器連接已建立 2: 請求已接收 3: 請求處理中 4: 請求已完成,且響應已就緒 xhr.responseText // 包含對請求的響應,如果請求未成功或尚未發送,則返回null xhr.timeout // 表示該請求的最大請求時間(毫秒),超過該時間請求會自動結束。 xhr.upload // 表示上傳過程。 // 常用方法 xhr.abort() // 中止請求 xhr.open() // 初始化一個請求。該方法只能JavaScript代碼中使用 xhr.setRequestHeader() // 設置HTTP請求頭的值 xhr.send() // 發送請求。如果請求是異步的(默認),那麽該方法將在請求發送後立即返回
更多關於XMLHttpRequest的信息點擊:XMLHttpRequest
FileReader
對象允許Web應用程序異步讀取存儲在用戶計算機上的文件(或原始數據緩沖區)的內容,使用 File 或 Blob 對象指定要讀取的文件或數據。
其中File對象可以是來自用戶在一個<input>元素上選擇文件後返回的FileList對象,也可以來自拖放操作生成的 DataTransfer對象,還可以是來自在一個HTMLCanvasElement上執行mozGetAsFile()方法後返回結果。
屬性:
FileReader.error // 表示在讀取文件時發生的錯誤 FileReader.readyState // 表示FileReader狀態的數字。0:還沒有加載任何數據; 1:數據正在被加載;2:已完成全部的讀取請求 FileReader.result // 文件的內容。該屬性僅在讀取操作完成後才有效,數據的格式取決於使用哪個方法來啟動讀取操作。
事件處理
FileReader.onabort= ()=>{} // 該事件在讀取操作被中斷時觸發
FileReader.onerror = ()=>{} // 該事件在讀取操作發生錯誤時觸發。
FileReader.onload = ()=>{} // 該事件在讀取操作完成時觸發。
FileReader.onloadstart = ()=>{} // 該事件在讀取操作開始時觸發
FileReader.onloadend = ()=>{} // 該事件在讀取操作結束時(要麽成功,要麽失敗)觸發
FileReader.onprogress = ()=>{} // 該事件在讀取Blob時觸發
方法
FileReader.abort() // 中止讀取操作。在返回時,readyState屬性為DONE
FileReader.readAsArrayBuffer() // 開始讀取指定的 Blob中的內容, 一旦完成, result 屬性中保存的將是被讀取文件的 ArrayBuffer 數據對象
FileReader.readAsDataURL() // 開始讀取指定的Blob中的內容。一旦完成,result屬性中將包含一個data: URL格式的字符串以表示所讀取文件的內容
FileReader.readAsText() // 開始讀取指定的Blob中的內容。一旦完成,result屬性中將包含一個字符串以表示所讀取的文件內容。
form表單上傳
<form id="uploadForm" method="POST" action="upload" enctype="multipart/form-data">
<input type="file" id="myFile" name="file" />
<input type="submit" value="提交" />
</form>
所有瀏覽器都支持的上傳方式,且submit提交後頁面會刷新。
action: 提交地址
enctype的常見類型(告訴服務器我們發送過去的數據是用哪種格式進行編碼的)
- application/x-www-form-urlencoded (默認數據編碼方式)
- multipart/form-data(復雜,但它允許在數據中包含整個文件,所以常用於文件上傳)
- text/plain(一般用於debug)
FormData + XHR2 + FileReader + canvas
【實現步驟】
- 監聽一個input(type=‘file’)的onchange事件,這樣獲取到文件file;
- 將file轉成dataUrl;
- 然後根據dataUrl利用canvas繪制圖片壓縮,然後再轉成新的dataUrl;
- 再把dataUrl轉成Blob;
- 把Blob append進FormData中;
- xhr實現上傳。
HTML代碼
<input type="file" name="file" accept=“image/*” onchange='handleInputChange(event)'>
1、監聽input的change事件
function handleInputChange (event) {
const file = event.target.files[0]; // 獲取當前選中的文件
const imgMasSize = 1024 * 1024 * 10; // 限制大小10MB
// 檢查文件類型
if(['jpeg', 'png', 'gif', 'jpg'].indexOf(file.type.split("/")[1]) < 0){
// 不支持該文件類型
}
// 文件大小限制
if(file.size > imgMasSize ) {
// 文件大小自定義限制
}
// 圖片壓縮處理函數
transformFileToDataUrl(file);
}
2、將file轉成dataUrl
function transformFileToDataUrl (file) {
const imgCompassMaxSize = 200 * 1024; // 超過 200k 就壓縮
// 存儲文件相關信息
imgFile.type = file.type || 'image/jpeg';
imgFile.size = file.size;
imgFile.name = file.name;
imgFile.lastModifiedDate = file.lastModifiedDate;
// 封裝好的函數
const reader = new FileReader();
// file轉dataUrl是個異步函數,onload表示讀取完成了
reader.onload = function(e) {
const result = e.target.result;
if(result.length < imgCompassMaxSize) {
compress(result, processData, false ); // 圖片不壓縮
} else {
compress(result, processData); // 圖片壓縮
}
};
reader.readAsDataURL(file);
}
3、canvas進行壓縮的處理
function compress (dataURL, callback, shouldCompress = true) {
const img = new window.Image(); // new 一個圖片對象
img.src = dataURL; // 通過fileReader讀取到的base64數據
img.onload = function () {
// 1、創建canvas上下文
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 獲取圖片寬高賦值給canvas繪圖
canvas.width = img.width;
canvas.height = img.height;
// 繪制出一張canvas圖片
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
let compressedDataUrl;
if(shouldCompress){
compressedDataUrl = canvas.toDataURL(imgFile.type, 0.2); // 後面的系數是繪圖輸出圖片質量
} else {
compressedDataUrl = canvas.toDataURL(imgFile.type, 1); // 不改變原圖質量
}
document.getElementById('preview').appendChild(img);
callback(compressedDataUrl); // 最後圖片壓縮好後,去進行base64 -> blob的轉換(傳遞到後臺)
}
}
4、把Blob append進FormData中;
function processData (dataURL) {
const binaryString = window.atob(dataURL.split(',')[1]); // window.atob對用base-64編碼過的字符串進行解碼
const arrayBuffer = new ArrayBuffer(binaryString.length); // ArrayBuffer 對象用來表示通用的、固定長度的原始二進制數據緩沖區。ArrayBuffer 不能直接操作,而是要通過類型數組對象或 DataView 對象來操作,它們會將緩沖區中的數據表示為特定的格式,並通過這些格式來讀寫緩沖區的內容。
const intArray = new Uint8Array(arrayBuffer); // Uint8Array類型化數組表示一個由8位無符號整數組成的數組。內容初始化為0。一旦建立,您可以使用對象的方法或使用標準數組索引語法(即使用括號符號)引用數組中的元素。
for (let i = 0, j = binaryString.length; i < j; i++) {
intArray[i] = binaryString.charCodeAt(i);
}
const data = [intArray];
let blob;
// 通過兼容性判斷,最後轉換為二進制數據
try {
blob = new Blob(data, { type: imgFile.type });
} catch (error) {
window.BlobBuilder = window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder;
if (error.name === 'TypeError' && window.BlobBuilder){
const builder = new BlobBuilder();
builder.append(arrayBuffer);
blob = builder.getBlob(imgFile.type);
} else {
throw new Error('版本過低,不支持上傳圖片');
}
}
// blob 轉 file
const fileOfBlob = new File([blob], imgFile.name); // File 對象是特殊類型的 Blob,且可以用在任意的 Blob 類型的 context 中。
const formData = new FormData(); // 把要傳輸的數據添加到FormData()對象中
// type
formData.append('type', imgFile.type);
// size
formData.append('size', fileOfBlob.size);
// name
formData.append('name', imgFile.name);
// lastModifiedDate
formData.append('lastModifiedDate', imgFile.lastModifiedDate);
// append 文件
formData.append('file', fileOfBlob);
uploadImg(formData); // 調用xhr發送數據到後臺
}
5、xhr實現上傳
function uploadImg (formData) {
const xhr = new XMLHttpRequest();
// 進度監聽
xhr.upload.addEventListener('progress', (e)=>{
console.log(e, e.loaded , e.total); // 可以利用這兩個對象算出目前的傳輸比例
}, false);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
const result = JSON.parse(xhr.responseText);
if (xhr.status === 200) {
// 上傳成功
console.log(result);
} else {
// 上傳失敗
}
}
};
xhr.open('POST', '/upload' , true); // 中間"/upload"為後臺上傳地址(如果需要兼容性強可以使用限制的ajax庫)
xhr.send(formData); // 發送到後臺
}
代碼github訪問地址:上傳實例代碼
小結
通過本節內容,我們應該徹底的理解了前端上傳是如何實現的,給出的代碼實例雖然簡單,但是已經是非常核心,我們可以通過這個版本去實現一個非常復雜的需求,譬如多圖片上傳,那麽也就是遍歷的調用transformFileToDataUrl這個方法去實現。在例如添加上拖拽文件到指定區域去上傳,那麽我們只需要了解下drag對象就可以很輕松的實現了。
js文件上傳原理(form表單 ,FormData + XHR2 + FileReader + canvas)