Docs
18.4-Blob-File-APIs
18.4 Blob and File APIs
Overview
The Blob and File APIs provide powerful mechanisms for handling binary data and files in JavaScript. Blobs (Binary Large Objects) represent raw data, while File objects extend Blob with file-specific metadata.
Learning Objectives
- •Create and manipulate Blob objects
- •Work with File objects and FileReader
- •Handle file uploads and downloads
- •Convert between data formats
- •Process files in the browser
Core Concepts
Blob Basics
// Create a Blob from text
const textBlob = new Blob(['Hello, World!'], { type: 'text/plain' });
// Create a Blob from JSON
const jsonBlob = new Blob([JSON.stringify({ name: 'John', age: 30 })], {
type: 'application/json',
});
// Create a Blob from multiple parts
const multiPartBlob = new Blob(['Part 1\n', 'Part 2\n', 'Part 3'], {
type: 'text/plain',
});
// Blob properties
console.log(textBlob.size); // Size in bytes
console.log(textBlob.type); // MIME type
File Object
// File extends Blob with name and metadata
// Files are typically obtained from input elements or drag & drop
// File from input
const input = document.querySelector('input[type="file"]');
input.addEventListener('change', (e) => {
const file = e.target.files[0];
console.log(file.name); // File name
console.log(file.size); // Size in bytes
console.log(file.type); // MIME type
console.log(file.lastModified); // Timestamp
});
// Create File programmatically
const file = new File(['File content here'], 'example.txt', {
type: 'text/plain',
lastModified: Date.now(),
});
FileReader API
// Read file as text
function readAsText(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);
reader.readAsText(file);
});
}
// Read file as data URL (base64)
function readAsDataURL(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file);
});
}
// Read file as ArrayBuffer
function readAsArrayBuffer(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);
reader.readAsArrayBuffer(file);
});
}
// Usage with progress
function readWithProgress(file, onProgress) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onprogress = (e) => {
if (e.lengthComputable) {
onProgress((e.loaded / e.total) * 100);
}
};
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);
reader.readAsArrayBuffer(file);
});
}
Blob URLs
// Create a Blob URL
const blob = new Blob(['Hello, World!'], { type: 'text/plain' });
const blobUrl = URL.createObjectURL(blob);
// Use Blob URL
const link = document.createElement('a');
link.href = blobUrl;
link.download = 'hello.txt';
link.click();
// Always revoke when done
URL.revokeObjectURL(blobUrl);
// Image preview example
function previewImage(file) {
const url = URL.createObjectURL(file);
const img = document.createElement('img');
img.src = url;
img.onload = () => URL.revokeObjectURL(url);
return img;
}
Data Conversion
Text Conversions
// Blob to Text
async function blobToText(blob) {
return blob.text(); // Modern browsers
// Or: return new Response(blob).text();
}
// Text to Blob
function textToBlob(text, type = 'text/plain') {
return new Blob([text], { type });
}
// Blob to Base64
async function blobToBase64(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
// Base64 to Blob
function base64ToBlob(base64, type) {
const byteCharacters = atob(base64.split(',')[1] || base64);
const byteNumbers = new Uint8Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
return new Blob([byteNumbers], { type });
}
ArrayBuffer Conversions
// Blob to ArrayBuffer
async function blobToArrayBuffer(blob) {
return blob.arrayBuffer(); // Modern browsers
}
// ArrayBuffer to Blob
function arrayBufferToBlob(buffer, type) {
return new Blob([buffer], { type });
}
// String to ArrayBuffer
function stringToArrayBuffer(str) {
return new TextEncoder().encode(str).buffer;
}
// ArrayBuffer to String
function arrayBufferToString(buffer) {
return new TextDecoder().decode(buffer);
}
Blob Slicing
// Slice a Blob
const largeBlob = new Blob(['0123456789'], { type: 'text/plain' });
const slice = largeBlob.slice(0, 5); // First 5 bytes
const middle = largeBlob.slice(3, 7); // Bytes 3-6
const end = largeBlob.slice(5); // From byte 5 to end
// Chunked reading
async function readInChunks(blob, chunkSize = 1024 * 1024) {
const chunks = [];
let offset = 0;
while (offset < blob.size) {
const chunk = blob.slice(offset, offset + chunkSize);
const text = await chunk.text();
chunks.push(text);
offset += chunkSize;
}
return chunks;
}
File Downloads
// Download text file
function downloadText(content, filename) {
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
// Download JSON
function downloadJSON(data, filename) {
const json = JSON.stringify(data, null, 2);
const blob = new Blob([json], { type: 'application/json' });
downloadBlob(blob, filename);
}
// Download Blob
function downloadBlob(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// Download from fetch response
async function downloadFromUrl(url, filename) {
const response = await fetch(url);
const blob = await response.blob();
downloadBlob(blob, filename);
}
File Uploads
// Upload with FormData
async function uploadFile(file, url) {
const formData = new FormData();
formData.append('file', file);
formData.append('name', file.name);
const response = await fetch(url, {
method: 'POST',
body: formData,
});
return response.json();
}
// Upload with progress
function uploadWithProgress(file, url, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('file', file);
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
onProgress((e.loaded / e.total) * 100);
}
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(xhr.statusText));
}
};
xhr.onerror = () => reject(new Error('Upload failed'));
xhr.open('POST', url);
xhr.send(formData);
});
}
// Chunked upload
async function uploadInChunks(file, url, chunkSize = 1024 * 1024) {
const totalChunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', i);
formData.append('totalChunks', totalChunks);
formData.append('fileName', file.name);
await fetch(url, {
method: 'POST',
body: formData,
});
}
}
Image Processing
// Resize image
async function resizeImage(file, maxWidth, maxHeight) {
return new Promise((resolve) => {
const img = new Image();
const url = URL.createObjectURL(file);
img.onload = () => {
URL.revokeObjectURL(url);
let { width, height } = img;
if (width > maxWidth) {
height *= maxWidth / width;
width = maxWidth;
}
if (height > maxHeight) {
width *= maxHeight / height;
height = maxHeight;
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob(resolve, file.type, 0.9);
};
img.src = url;
});
}
// Compress image
async function compressImage(file, quality = 0.7) {
return new Promise((resolve) => {
const img = new Image();
const url = URL.createObjectURL(file);
img.onload = () => {
URL.revokeObjectURL(url);
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
canvas.toBlob(resolve, 'image/jpeg', quality);
};
img.src = url;
});
}
File Type Detection
// Check by extension
function getFileType(filename) {
const ext = filename.split('.').pop().toLowerCase();
const types = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
gif: 'image/gif',
pdf: 'application/pdf',
json: 'application/json',
txt: 'text/plain',
html: 'text/html',
css: 'text/css',
js: 'application/javascript',
};
return types[ext] || 'application/octet-stream';
}
// Check by magic bytes
async function detectFileType(file) {
const header = file.slice(0, 4);
const buffer = await header.arrayBuffer();
const bytes = new Uint8Array(buffer);
// Check magic numbers
if (bytes[0] === 0xff && bytes[1] === 0xd8) {
return 'image/jpeg';
}
if (
bytes[0] === 0x89 &&
bytes[1] === 0x50 &&
bytes[2] === 0x4e &&
bytes[3] === 0x47
) {
return 'image/png';
}
if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) {
return 'image/gif';
}
if (
bytes[0] === 0x25 &&
bytes[1] === 0x50 &&
bytes[2] === 0x44 &&
bytes[3] === 0x46
) {
return 'application/pdf';
}
return file.type || 'application/octet-stream';
}
Best Practices
- •Revoke Blob URLs - Prevent memory leaks
- •Handle large files - Use chunked reading/uploading
- •Validate file types - Check MIME types and magic bytes
- •Show progress - Provide feedback for large operations
- •Error handling - Handle read/upload failures gracefully
Summary
| Concept | Description |
|---|---|
| Blob | Raw binary data with optional type |
| File | Blob with name and metadata |
| FileReader | Read file contents asynchronously |
| Blob URL | Temporary URL for blob data |
| DataURL | Base64-encoded data inline |