1. 程式人生 > >js文件上傳原理(form表單 ,FormData + XHR2 + FileReader + canvas)

js文件上傳原理(form表單 ,FormData + XHR2 + FileReader + canvas)

創建 取數 dir wim text 括號 || val 常見類

目錄
  • form表單上傳
  • FormData + XHR2 + FileReader + canvas 無刷新本地預覽壓縮上傳實例

目前實現上傳的方式

瀏覽器小於等於IE9(低版本瀏覽器)使用下面的方式實現的

  1. flash實現(主流插件的方式,本文不涉及)
  2. 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

【實現步驟】

  1. 監聽一個input(type=‘file’)的onchange事件,這樣獲取到文件file;
  2. 將file轉成dataUrl;
  3. 然後根據dataUrl利用canvas繪制圖片壓縮,然後再轉成新的dataUrl;
  4. 再把dataUrl轉成Blob;
  5. 把Blob append進FormData中;
  6. 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)