跳至主要内容

[WebAPIs] Blob, File 和 FileReader

keywords: file, blob, ArrayBuffer, FIleList, FileReader

關聯文章:

  • [TypedArray, ArrayBuffer, DataView](/Users/pjchen/Projects/Notes/source/_posts/JavaScript/[JS] TypedArray, ArrayBuffer 和 DataView.md)
  • [檔案上傳(Input File) and File Upload](/Users/pjchen/Projects/Notes/source/_posts/WebAPIs/[WEB] 檔案上傳 Input File and File Upload.md)
  • [Blob and File API](/Users/pjchen/Projects/Notes/source/_posts/WebAPIs/[WEB] Blob and File API.md)

Blob 概念

keywords: new Blob(), Blob.prototype.size, Blob.prototype.type, Blob.prototype.slice()

Blob 是 Binary Large Object 的縮寫,表示的是二進位檔案的資料內容,透過 Blob,JavaScript 才能讀寫二進位資料的檔案。和 ArrayBuffer 不同的地方是,ArrayBuffer 是操作記憶體,而 Blob 是用來操作二進位檔案(但尚未參照到實體檔案上)

Blob 物件指的是能夠用來表徵檔案,帶有許多資料位元的 chunks,但它實際上並沒有參照到真正的檔案。Blob 物件和檔案一樣,它有自己的檔案大小和 MIME 格式,它會根據瀏覽器或 blob size 儲存在記憶體或檔案系統中使用上,它就和使用檔案一樣

Blob 的內容可以轉換成 ArrayBuffer ,因此可以輕易的把它存成二元資料

  • 想從 non-blob 物件或資料建立一個 Blob 可以使用 Blob() 建構式。
  • 想從已經包含 blob 的資料中建立 Blob 可以使用 slice() 方法。
  • 想從使用者系統中取得檔案的 Blob 物件,需要參考 File 文件。

建立 Blob 物件

透過 Blob() 建構式,可以從其他物件建立出 Blob 物件,Blob 物件會包含 typesize 兩者屬性可以使用:

// new Blob(array [, options])
// - array 是字元串或二進位物件(ArrayBuffer, ArrayBufferView, Blob, DOMString)
// - options 用來指定 MIME Type

var htmlFragment = '<div><h1>Hello Blob</h1></div>';
var htmlBlob = new Blob([htmlFragment], { type: 'text/html' });

// 常用的檔案格式
var jsonData = JSON.stringify({ hello: 'world' }, null, 2);
new Blob([jsonData], { type: 'application/json' });

var cssFormat = 'body { background-color: yellow; }';
new Blob([cssFormat], { type: 'text/css' });

array 可以是由 ArrayBuffer, ArrayBufferView, Blob, DOMString 這些物件組成的陣列。

範例

var debug = { hello: 'world' };
var blob = new Blob([JSON.stringify(debug, null, 2)], {
type: 'application/json',
});

console.log(blob); // Blob(22) {size: 22, type: "application/json"}

Blob() 建構式 @ MDN - Web APIs

複製 Blob (Blob.prototype.slice)

透過 Blob 的 slice 方法可以複製 Blob:

blob.slice(startByte, endByte, contentType);

讀取 Blob(FileReader)

透過 FileBlob 物件可以指定讓網頁讀取的檔案,接著透過 FileReader 物件可以讓網頁以非同步的方式讀取存在電腦的實體檔案內容。

File 物件的取得可以靠 <input type="file"> 或拖拉的 DataTransfer 物件等方式取得。

要讀取 Blob 內容的唯一方式是透過 FileReader ,透過 FileReader 可以將 Blob 轉換成 TypedArray:

/**
* extracting data from a Blob
* https://developer.mozilla.org/en-US/docs/Web/API/Blob
* 透過 readAsArrayBuffer 可以將 blob 讀取出來成為 typed Array
* FileReader.readyState
* - 0 表示尚未載入任何資料
* - 1 表示資料正在載入
* - 2 表示載入完成
**/

var reader = new FileReader();
reader.addEventListener('load', function () {
console.log(reader.result);
// reader.result contains the contents of blob as a typed array
});
reader.readAsArrayBuffer(blob);

範例: Using files from web applications

方法(Methods)

下述方法可以將 File / Blob 物件轉換成 TypedArray

  • FileReader.readAsArrayBuffer():以 TypedArray 的方式顯示檔案內容。
  • FileReader.readAsDataURL():以 data:<MIME Type>;base64,... 的方式顯示檔案內容,讀取的資料是圖檔的話會轉成 Base64,**可以直接用於 <img src="BASE_64"> 中;讀取的資料是 CSS 等若無法直接辨識的檔案類型,會轉成 application/octet-stream 的 Base 64 檔案。
  • FileReader.readAsText():以純文字的方式顯示檔案內容,預設是 UTF-8。
  • FileReader.readAsBinaryString():以原始二進制顯示檔案內容。

⚠️ 這個 DataURL 取得的字串不能直接進行 Base64 解碼,必須把前綴 data:*/*;base64, 從字串裡刪除以後,再進行解碼。

事件(Events)

var reader = new FileReader();

// 成功時促發
reader.addEventListener('load', function () {
console.log(reader.result);
});

// 成功失敗都會促發
reader.addEventListener('loadend', function () {
// ...
});

// 失敗時促發
reader.addEventListener('error', function () {
// ...
});

// reader.result contains the contents of blob as a typed array
reader.readAsArrayBuffer(blob);

範例

Sample Code @ CodePen

將圖片轉成 Base 64(image to Base64)

const uploader = document.querySelector('#uploader');
const previewImg = document.querySelector('#preview');
const fileReader = new FileReader();

uploader.addEventListener('change', (e) => {
const file = e.target.files[0]; // 取得 File Object
fileReader.readAsDataURL(file); // 將 File Object 讀取成 DataURL
});

// load 時可以取得 fileReader 回傳的結果
fileReader.addEventListener('load', function () {
const dataURL = fileReader.result; // Base64 Image
previewImg.src = dataURL;
});

Example Code @ CodePen

儲存 Blob(createObjectURL)

透過 window.URL.createObjectURL(blob) 可以取得 Blob URL,該網址的開頭會是 blob:// 開頭,由於 Blob URL 就和一般的 URL 相同,因此可以直接透過 window.open(blobURL) 或建立成連結後進行下載:

const data = { number: 42, string: 'hello, world', date: new Date() };
var JSONData = [JSON.stringify(data)];
var JSONBlob = new Blob(JSONData, { type: 'application/json' });

saveData(JSONBlob, 'blob2json.json');
openData(JSONBlob);

function openData(blob) {
// 將 blob 放到 URL 上
url = window.URL.createObjectURL(blob);
window.open(url);
}

function saveData(blob, fileName) {
const a = document.createElement('a');
document.body.appendChild(a);
a.style = 'display: none';

// 將 blob 放到 URL 上
url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();

// 釋放記憶體
a.href = '';
window.URL.revokeObjectURL(url);
}

參考:Saving binary data as file using JavaScript from a browser @ StackOverFlow

// 將 htmlBlob 儲存成實體檔
var htmlFragment = ['<div><h1>Hello</h1></div>'];
var htmlBlob = new Blob(htmlFragment, { type: 'text/html' });

saveData(htmlBlob, 'blob2html.html');

function saveData(blob, fileName) {
const a = document.createElement('a');
document.body.appendChild(a);
a.style = 'display: none';

// 將 blob 放到 URL 上
url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();

// 釋放記憶體
a.href = '';
window.URL.revokeObjectURL(url);
}

建立 Blob URL

var blob = new Blob(['body { background-color: yellow; }'], { type: 'text/css' });

var link = document.createElement('link');
link.rel = 'stylesheet';
//createObjectURL returns a blob URL as a string.
link.href = window.URL.createObjectURL(blob);
document.body.appendChild(link);

直接打開 Blob URL

建立好 DataURI 後也可以使用 window.open 直接把它打開:

const buffer = new ArrayBuffer(2);
const bytes = new Uint8Array(buffer);

bytes[0] = 65;
bytes[1] = 66;

const blob = new Blob([buffer], { type: 'text/plain' });
const dataUri = window.URL.createObjectURL(blob);
window.open(dataUri);

將 Blob URL (dataURL) 轉回到 Blob

function dataURItoBlob(dataURI) {
// convert base64 to raw binary data held in a string
// doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
var byteString = atob(dataURI.split(',')[1]);

// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

// write the bytes of the string to an ArrayBuffer
var ab = new ArrayBuffer(byteString.length);

// create a view into the buffer
var ia = new Uint8Array(ab);

// set the bytes of the buffer to the correct values
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}

// write the ArrayBuffer to a blob, and you're done
var blob = new Blob([ab], { type: mimeString });
return blob;
}

Blob from DataURL? @ StackOverflow

AJAX 請求 Blob

透過設定 xhr.responseType = 'blob' 可以請求回應的內容為 blob:

var xhr = new XMLHttpRequest();
xhr.open('GET', '/favicon.png');
xhr.responseType = 'blob'; //force the HTTP response, response-type header to be blob
xhr.onload = function () {
analyze_data(xhr.response); // the response will be a blob
};
xhr.send();

function analyze_data(blob) {
var myReader = new FileReader();
myReader.readAsArrayBuffer(blob);

myReader.addEventListener('loadend', function (e) {
var buffer = e.srcElement.result; // ArrayBuffer object
});
}

將 Blob 轉成 Buffer 後儲存

https://stackoverflow.com/a/14737423/5135452

  const blob = /* some blob */;
const buffer = await blob.arrayBuffer();
const bufferView = Buffer.from(buffer);

fs.writeFileSync('./foo', bufferView);

MIME Type

Multipurpose Internet Mail Extensions (MIME) type 是用來指稱檔案格式的標準化方式。瀏覽器通常使用 MIME type(而非 file extension)來決定要如何處理這份文件,因此,在伺服器的 response header 上設置適當地 MIME type 是很重要的。

Check all Media types @ IANA MIME Type @ MDN > Web > HTTP