From: 011netservice@gmail.com Date: 2022-04-24 Subject: Readme-UploadImagePreviewImmediately.txt 使用 Blob 和 File 相關 Web API 即時呈現上傳圖片檔案 歡迎來信交流. ---------- 2021-10-11 完整檔案: 參考 http://www.011.idv.tw/DHelper/UploadImagePreviewImmediately.html ---------- 2021-10-11 使用 Blob 和 File 相關 Web API 即時呈現上傳圖片檔案 https://jiepeng.me/2018/04/17/use-blob-and-file-web-api-create-upload-image-preview-immediately Blob Blob(Binary Large Object)類型的物件是一個不可變的原始資料(Immutable Raw Data),類似檔案的二進制資料,通常像是一些圖片、音訊或者是可執行的二進制程式碼也可以被儲存為 Blob。 Blob 的 constructor 接受兩個參數,第一個是實際資料的陣列,第二個則是資料的類型,但這兩個參數不是必須的: Blob(blobParts[, options]) blobParts 是一個陣列類型,可以是以下的元素: Array ArrayBuffer ArrayBufferView Blob DOMString Options 是一個物件類型,可以設定以下兩種屬性: type - 預設為空值,代表會被放入在 Blob 中的 MIME 類型的內容陣列 endings - 預設為 transparent 讓我們來建立一個簡單的 Blob: const url = 'https://www.google.com.tw'; const data = new Blob(url); console.log(data); // Blob { size: 22, type: "" } 透過 console.log 我們可以看到,我們可以從 Blob 讀取兩個屬性:size、type size:二進制資料的大小,單位為 bytes type:MIME 的格式類型。如果不知道型態,則為空字串 在 console.log 中我們還看到了另一個 slice 的方法,這裡的 slice 方法我們把它想成是 Array 的 slice,它會回傳一個全新的 Blob 物件,並不會影響到原本的 Blob,我們根據上面的範例來測試: console.log(data); // Blob { size: 22, type: "" } const sliceBlob = data.slice(0, 10); // Blob { size: 10, type: "" } console.log(data); // Blob { size: 22, type: "" } slice 方法對於內容較大的 Blob 可以將資料以固定大小切分成多塊,例如使用者上傳了一個很大的檔案,伺服器當下可能無法接收這麼大的檔案內容,這時候如果有事先進行分塊並且分批上傳,就可以有效減輕伺服器的負擔。 File 實際上 File 繼承了 Blob,所以除了原來的 size 和 type 屬性外,File 還多了以下的屬性: name lastModified lastModifiedDate webkitRelativePath 別忘了原來的 slice 方法也還能繼續使用! FileList FileList 型別的物件通常是來自 HTML input 元素的 files 屬性,每個 input 都會有一個 files 的屬性。FileList 其實就是多個 File 的集合,例如我們需要上傳檔案我們 HTML 可以寫成: 💡 Tips:如果我們需要上傳多個檔案的話可以在 input 再加上 multiple 屬性。 FileList 提供了一個 item 方法,和一個 length 的屬性,item 方法可以得到在陣列內的某個 File,length 則是回傳整個 FileList 的長度。 接下來讓我們使用以上的這幾個 Web API 來建立一個圖片上傳並即時呈現的範例! 範例 首先讓我們建立一個 Container,裡面放置一個上傳的按鈕和一個空的 img:
本範例不使用 jQuery 而是直接使用 JavaScirpt 原生的 API 來操作 DOM 🕹 現在我們建立好了一個簡單的 HTML,接下來我們需要寫 JavaScript 來操作我們的 DOM: const uploadButton = document.getElementById('upload'); const imgDOM = document.getElementById('upload-img'); function handleFiles() { const fileList = this.files; console.log(fileList); } uploadButton.addEventListener('change', handleFiles, false); 我們在 uploadButton 註冊一個 Event Listenter 來監聽變化,當我們選擇一個檔案時,會觸發 change 事件,這時候執行 handleFiles callback,我們現在裡面簡單的印出 fileList,透過 console.log 你可看到如下的訊息: ▶︎ FileList [ File ] 這時候可以看到 FileList 內有一個 File 的物件,我們可以取出這個物件,並操作它: const uploadButton = document.getElementById('upload'); const imgDOM = document.getElementById('upload-img'); function handleFiles() { const fileList = this.files; const [file] = fileList; // 取出 File } uploadButton.addEventListener('change', handleFiles, false); 這時候你可能會想 File 所提供的屬性並沒有檔案的 URL 或者是位置,根本沒辦法把圖片印出來,這時候我要透過另一個 URL 的 Web API 來處理。 URL URL 提供了建立 URL 的方法,你可以透過 new URL() 的方式來建立,這裡我們使用它的靜態方法 createObjectURL。 createObjectURL 接受一個 File 或是 Blob 的物件: const objectURL = URL.createObjectURL(blob); 所以我們可以繼續完成上面的範例: const uploadButton = document.getElementById('upload'); const imgDOM = document.getElementById('upload-img'); function createImageFromFile(img, file) { return new Promise((resolve, rejfect) => { img.src = URL.createObjectURL(file); img.onload = () => { URL.revokeObjectURL(img.src); resolve(img); }; img.onerror = () => reject('Failure to load image.'); }); } function handleFiles() { const fileList = this.files; const [file] = fileList; createImageFromFile(imgDOM, file).then(img => console.log(img)); } uploadButton.addEventListener('change', handleFiles, false); 我建立了一個 createImageFromFile 的函式,它接收一個 img 和 file 參數,img 就是該圖片的 DOM 元素,file 則是我們上傳的 File 物件,它回傳一個 Promise,我們可透過回傳的結果再對圖片加以的調整,例如我們可以像是這樣調整圖片的大小: function handleFiles() { const fileList = this.files; const [file] = fileList; createImageFromFile(imgDOM, file).then(img => { img.width = 150; img.height = 150; }); } 請注意這裡使用到了 URL.revokeObjectURL,當我們得到一個 Blob 作為圖片的來源時(也就是在 onload 的條件下),我們可以呼叫 revokeObjectURL 撤銷對 img.src 的參考,因為 creatObjectURL 只是一個將來源的 src 和媒體(Media)的實例連結起來的一種方法,revokeObjectURL 會留下底層的物件,並在適當的時間處理 Garbage Collection 🚚。 這樣我們就完成一個選擇上傳圖片並即時呈現的簡單效果了!🎉 FileReader 我們前面過有提到了 File 這個物件,物件,我們再稍微介紹他相關的 API 叫做 FileReader。 FileReader 物件是可以讓網頁非同步的去讀取在客戶端的檔案,或是原始暫存的資料,所以 FileReader 所接受的參數就是 File 和 Blob 的物件。 屬性 Property Description error DOMException 類型的物件記錄了讀取資料時發生的錯誤資訊 result 讀取到的資料內容,資料格式則是根據使用的讀取方法 readyState Empty = 0(尚未讀取資料)、Loading = 1(讀取資料)、Done = 2(完成讀取) 事件處理器 FileReader 繼承自 EventTarget,所以可以透過 addEventListenter 方法來註冊 Listener。 Handler Name Description onboard 讀取中斷時被觸發 onerror 讀取錯誤時被觸發 onload 讀取完成時被觸發 onloadstart 讀取開始時被觸發 onloadprogress 讀取 Blob 時被觸發 onloadend 讀取結束後被觸發(不管讀取 success 或 failure) 方法 Method Name Description readAsText 讀取 Blob 完成後,result 將會是文字表示讀入資料的內容 readAsDataURL 讀取 Blob 完成後,result 將會是 Base64 編碼表示讀入資料的內容 readAsBinaryString 讀取 Blob 完成後,result 將會是原始二進制資料表示讀入資料的內容 readAsBufferArray 讀取 Blob 完成後,result 將會是 ArrayBuffer 物件來表示讀入資料的內容 abort 中斷讀取,此方法回傳後屬性 readyState 為 DONE 以上是 FileReader 的相關屬性和方法,我們再來改寫上面的範例,使用 FileReader 來得到 Base64 編碼的結果,這裡我們需要用到 readAsDataURL 方法。 改寫範例 首先我們先新增一個 getFileBase64Encode 方法: function getFileBase64Encode(blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(blob); reader.onload = () => resolve(reader.result); reader.onerror = error => reject(error); }); } 這裡使用了 Promise,當檔案成功讀取後,使用 resovle 來回傳 reader 的結果,若是讀取中發生錯誤,我們透過 reject 來拋出錯誤。 接著在 handeFiles 加入這個函式: function handleFiles() { const fileList = this.files; const [file] = fileList; createImageFromFile(imgDOM, file).then(img => { img.width = 150; img.height = 150; }); getFileBase64Encode(file).then(b64 => console.log(b64)); } 接著在 console.log 就可以看到圖片 Base64 編碼後的結果了! 完整範例 HTML: `Blob` and `File` Web API Example

Blob and File Web API Example


JavaScript: const uploadButton = document.getElementById('upload'); const imgDOM = document.getElementById('upload-image'); function createImageFromFile(img, file) { return new Promise((resolve, rejfect) => { img.src = URL.createObjectURL(file); img.onload = () => { URL.revokeObjectURL(img.src); resolve(img); }; img.onerror = () => reject('Failure to load image.'); }); } function getFileBase64Encode(blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(blob); reader.onload = () => resolve(reader.result); reader.onerror = error => reject(error); }); } function handleFiles() { const fileList = this.files; const [file] = fileList; const p1 = createImageFromFile(imgDOM, file); const p2 = getFileBase64Encode(file); Promise.all([p1, p2]) .then(result => { const [img, b64] = result; // fixed image width, height img.width = 150; img.height = 150; console.log(b64); }); } uploadButton.addEventListener('change', handleFiles, false); CSS: body { font-family: 'Source Code Pro'; } #app { margin-top: 25px; } .file-label { width: 15rem; } input[type=file].hidden { visibility: hidden; width: 15rem; margin-left: -15rem; }