How to Work with Blobs in JavaScript
In the previous guides, you learned about ArrayBuffer for working with raw binary data at the byte level and TextEncoder/TextDecoder for converting between binary and text. But there is another binary data type in JavaScript that serves a fundamentally different purpose: the Blob.
A Blob (Binary Large Object) represents a chunk of immutable raw data that typically comes from or goes to somewhere outside JavaScript: files selected by the user, images downloaded from a server, data generated for download, content pasted from the clipboard, or media streams captured from a camera. While ArrayBuffer is designed for low-level byte manipulation in memory, Blob is designed for transporting binary data between JavaScript and the outside world.
Blobs are everywhere in browser APIs. The File API returns Blobs. The Fetch API can send and receive Blobs. Canvas can export images as Blobs. The Clipboard API works with Blobs. Understanding how to create, transform, and use Blobs is essential for building applications that handle files, images, downloads, and binary data exchange.
What Is a Blob? (Immutable Raw Data)
A Blob is an immutable, opaque object that represents raw binary data with an associated MIME type. Let's break down what each of those characteristics means.
Immutable: Once created, a Blob's content cannot be changed. You cannot modify individual bytes. To "change" a Blob, you create a new one from the existing data (or portions of it) combined with new data.
Opaque: You cannot directly read a Blob's bytes through indexing or properties. To access the data inside a Blob, you must explicitly convert it using methods like arrayBuffer(), text(), stream(), or through a FileReader.
MIME type: Every Blob carries an optional type property that describes the format of its content (e.g., "image/png", "text/plain", "application/json"). This type is used by the browser when the Blob is downloaded, displayed, or sent over the network.
Blob vs. ArrayBuffer
These two binary types serve different purposes:
| Feature | ArrayBuffer | Blob |
|---|---|---|
| Mutability | Mutable (via views) | Immutable |
| Direct byte access | Yes (via typed arrays/DataView) | No (must convert first) |
| MIME type | No | Yes (type property) |
| Size limit | Limited by available memory | Can represent very large data |
| Primary use | In-memory byte manipulation | File I/O, downloads, uploads, transport |
| Can be sent via fetch | Yes (as body) | Yes (as body) |
| Can create a URL | No | Yes (URL.createObjectURL) |
| Used by File API | No | Yes (File extends Blob) |
Think of ArrayBuffer as a workbench where you directly manipulate bytes, and Blob as a sealed package labeled with its content type, ready to be sent, saved, or displayed.
The File Object Extends Blob
The File object (returned by <input type="file"> and drag-and-drop) is a subclass of Blob with additional properties like name, lastModified, and webkitRelativePath:
// <input type="file" id="fileInput">
let fileInput = document.getElementById("fileInput");
fileInput.addEventListener("change", (event) => {
let file = event.target.files[0];
console.log(file instanceof Blob); // true (File IS a Blob)
console.log(file instanceof File); // true
// Blob properties
console.log(file.size); // e.g., 524288 (bytes)
console.log(file.type); // e.g., "image/png"
// Additional File properties
console.log(file.name); // e.g., "photo.png"
console.log(file.lastModified); // e.g., 1710500000000 (timestamp)
});
Creating Blobs
The Blob constructor accepts two arguments: an array of data parts and an optional options object specifying the MIME type.
Constructor Syntax
let blob = new Blob(blobParts, options);
blobParts: An array of data pieces. Each piece can be a string, anArrayBuffer, a typed array (Uint8Array, etc.), aDataView, or anotherBlob. The parts are concatenated in order.options: An optional object with:type: A string representing the MIME type (default:"")endings: How to handle newline characters in strings ("transparent"or"native", default:"transparent")
Creating a Blob from Strings
// A simple text blob
let textBlob = new Blob(["Hello, World!"], { type: "text/plain" });
console.log(textBlob.size); // 13
console.log(textBlob.type); // "text/plain"
// Multiple string parts are concatenated
let multiPartBlob = new Blob(
["Hello, ", "World", "!"],
{ type: "text/plain" }
);
console.log(multiPartBlob.size); // 13 (same as above)
// HTML content
let htmlBlob = new Blob(
["<h1>Title</h1><p>Paragraph content</p>"],
{ type: "text/html" }
);
// JSON content
let data = { name: "Alice", age: 30, city: "Paris" };
let jsonBlob = new Blob(
[JSON.stringify(data, null, 2)],
{ type: "application/json" }
);
// CSV content
let csvContent = "Name,Age,City\nAlice,30,Paris\nBob,25,London\n";
let csvBlob = new Blob([csvContent], { type: "text/csv" });
Creating a Blob from Binary Data
// From a Uint8Array
let bytes = new Uint8Array([72, 101, 108, 108, 111]);
let binaryBlob = new Blob([bytes], { type: "application/octet-stream" });
console.log(binaryBlob.size); // 5
// From an ArrayBuffer
let buffer = new ArrayBuffer(8);
new Float64Array(buffer)[0] = 3.14159;
let bufferBlob = new Blob([buffer]);
console.log(bufferBlob.size); // 8
// Mixing types: strings, typed arrays, and other blobs
let header = new Uint8Array([0x89, 0x50, 0x4E, 0x47]); // PNG magic bytes
let body = new Blob(["image data here"]);
let footer = new Uint8Array([0x00, 0x00]);
let combinedBlob = new Blob([header, body, footer], { type: "image/png" });
console.log(combinedBlob.size); // 4 + 15 + 2 = 21
Creating a Blob from Other Blobs (Concatenation)
Since Blobs are immutable, combining or modifying them means creating new Blobs:
let part1 = new Blob(["First part. "]);
let part2 = new Blob(["Second part. "]);
let part3 = new Blob(["Third part."]);
// Concatenate blobs into a new blob
let combined = new Blob([part1, part2, part3], { type: "text/plain" });
// Verify
combined.text().then(text => {
console.log(text); // "First part. Second part. Third part."
});
Slicing a Blob
The slice method creates a new Blob containing a portion of the original. It does not copy the data. The new Blob references the same underlying data:
let blob = new Blob(["Hello, World!"], { type: "text/plain" });
// slice(start, end, contentType)
let hello = blob.slice(0, 5, "text/plain");
let world = blob.slice(7, 12, "text/plain");
hello.text().then(t => console.log(t)); // "Hello"
world.text().then(t => console.log(t)); // "World"
// Without specifying contentType, the slice has an empty type
let noType = blob.slice(0, 5);
console.log(noType.type); // "" (empty)
Slicing is particularly useful for chunked uploads where you need to send a large file in pieces:
async function uploadInChunks(blob, chunkSize = 1024 * 1024) {
let offset = 0;
let chunkIndex = 0;
while (offset < blob.size) {
let chunk = blob.slice(offset, offset + chunkSize);
await fetch("/api/upload", {
method: "POST",
headers: {
"Content-Range": `bytes ${offset}-${offset + chunk.size - 1}/${blob.size}`,
"X-Chunk-Index": chunkIndex.toString()
},
body: chunk
});
offset += chunkSize;
chunkIndex++;
console.log(`Uploaded ${Math.min(offset, blob.size)} / ${blob.size} bytes`);
}
}
Blob Properties
Blobs have only two properties, both read-only:
let blob = new Blob(["Hello, World!"], { type: "text/plain" });
console.log(blob.size); // 13 (size in bytes)
console.log(blob.type); // "text/plain" (MIME type)
// Empty blob
let emptyBlob = new Blob([]);
console.log(emptyBlob.size); // 0
console.log(emptyBlob.type); // "" (empty string)
The size property returns the size in bytes, not characters. For UTF-8 encoded strings, multi-byte characters take more than one byte:
let asciiBlob = new Blob(["Hello"]);
console.log(asciiBlob.size); // 5 (1 byte per char)
let emojiBlob = new Blob(["Hello 👋"]);
console.log(emojiBlob.size); // 10 (5 ASCII bytes + 1 space + 4 bytes for emoji)
let cyrillicBlob = new Blob(["Привет"]);
console.log(cyrillicBlob.size); // 12 (2 bytes per Cyrillic letter in UTF-8)
Blob URLs: URL.createObjectURL(blob)
One of the most powerful features of Blobs is the ability to create Blob URLs (also called Object URLs). A Blob URL is a temporary URL that points to the Blob's data in memory. This URL can be used anywhere a regular URL is accepted: as the src of an <img>, the href of an <a>, the source of a <video>, or in CSS url().
Creating a Blob URL
let blob = new Blob(["Hello, World!"], { type: "text/plain" });
let url = URL.createObjectURL(blob);
console.log(url); // "blob:http://localhost:3000/550e8400-e29b-41d4-a716-446655440000"
The URL has the format blob:<origin>/<uuid>. It is valid only within the current document and cannot be accessed from other pages or after the page is closed.
Triggering a File Download
The most common use of Blob URLs is creating downloadable files in the browser without a server:
function downloadBlob(blob, filename) {
let url = URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = filename; // Suggests a filename for the download
a.click();
// Clean up after a short delay
setTimeout(() => URL.revokeObjectURL(url), 1000);
}
// Download a text file
let textBlob = new Blob(["Hello, World!\nThis is line 2."], { type: "text/plain" });
downloadBlob(textBlob, "hello.txt");
// Download a JSON file
let data = { users: [{ name: "Alice" }, { name: "Bob" }] };
let jsonBlob = new Blob(
[JSON.stringify(data, null, 2)],
{ type: "application/json" }
);
downloadBlob(jsonBlob, "users.json");
// Download a CSV file
let csv = "Name,Email,Age\nAlice,alice@example.com,30\nBob,bob@example.com,25\n";
let csvBlob = new Blob([csv], { type: "text/csv" });
downloadBlob(csvBlob, "users.csv");
Displaying Images
// Create an image from canvas and display it
let canvas = document.getElementById("myCanvas");
canvas.toBlob((blob) => {
let url = URL.createObjectURL(blob);
let img = document.createElement("img");
img.src = url;
img.onload = () => {
// Revoke after the image loads to free memory
URL.revokeObjectURL(url);
};
document.body.append(img);
}, "image/png");
// Display a user-selected image
let fileInput = document.getElementById("imageInput");
fileInput.addEventListener("change", (event) => {
let file = event.target.files[0];
if (!file || !file.type.startsWith("image/")) return;
let url = URL.createObjectURL(file);
let img = document.getElementById("preview");
img.src = url;
img.onload = () => URL.revokeObjectURL(url);
});
Using Blob URLs in CSS
// Create a CSS file as a Blob and apply it
let css = `
body { background-color: #1a1a2e; color: #e0e0e0; }
h1 { color: #e94560; }
`;
let cssBlob = new Blob([css], { type: "text/css" });
let cssUrl = URL.createObjectURL(cssBlob);
let link = document.createElement("link");
link.rel = "stylesheet";
link.href = cssUrl;
document.head.append(link);
Revoking Blob URLs: Memory Management
Every Blob URL holds a reference to the Blob in memory, preventing it from being garbage collected. If you create many Blob URLs without revoking them, you will leak memory. Always revoke Blob URLs when they are no longer needed:
let url = URL.createObjectURL(blob);
// Use the URL...
// When done, revoke it to free memory
URL.revokeObjectURL(url);
// After revoking, the URL becomes invalid
// Trying to load it will fail (e.g., img shows broken image icon)
Once you call URL.revokeObjectURL(url), the URL immediately becomes invalid. Any ongoing loads (images, stylesheets, iframes) that reference this URL will fail. Always revoke after the resource has been fully consumed.
// ❌ WRONG: Revoking too early
let url = URL.createObjectURL(imageBlob);
let img = document.createElement("img");
img.src = url;
URL.revokeObjectURL(url); // Image hasn't loaded yet! The load will fail!
document.body.append(img);
// ✅ CORRECT: Revoke after the image loads
let url2 = URL.createObjectURL(imageBlob);
let img2 = document.createElement("img");
img2.onload = () => URL.revokeObjectURL(url2); // Revoke after load completes
img2.onerror = () => URL.revokeObjectURL(url2); // Also revoke on error
img2.src = url2;
document.body.append(img2);
Blob URL vs. Data URL
There are two ways to represent binary data as a URL. Both work as src, href, etc., but they have significant differences:
| Feature | Blob URL | Data URL (base64) |
|---|---|---|
| Format | blob:origin/uuid | data:type;base64,AAAB... |
| Size in memory | Reference to the Blob | The entire data encoded in the string |
| Transmission | Short URL string | Very long string (33% larger than original) |
| Revocation needed | Yes (memory leak risk) | No (garbage collected with the string) |
| Persistence | Lost on page refresh | Can be stored in localStorage, attributes |
| Performance | Better for large data | Better for small data (< few KB) |
| Cross-page | Same origin only | Works anywhere (can be copied, stored) |
// Blob URL: efficient, requires cleanup
let blobUrl = URL.createObjectURL(blob);
// "blob:http://localhost/550e8400-..."
// Data URL: self-contained, larger
let reader = new FileReader();
reader.onload = () => {
let dataUrl = reader.result;
// "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=="
};
reader.readAsDataURL(blob);
Blob to ArrayBuffer, Blob to Base64
Since Blobs are opaque, you need explicit conversion methods to access their contents in different formats.
blob.arrayBuffer(): Blob to ArrayBuffer
Returns a Promise that resolves with the Blob's contents as an ArrayBuffer. Use this when you need to process the raw bytes:
let blob = new Blob(["Hello, World!"], { type: "text/plain" });
let buffer = await blob.arrayBuffer();
console.log(buffer.byteLength); // 13
// Now you can use typed arrays to read the bytes
let bytes = new Uint8Array(buffer);
console.log(bytes); // Uint8Array [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]
blob.text(): Blob to String
Returns a Promise that resolves with the Blob's contents as a UTF-8 string. This is the simplest way to read text from a Blob:
let blob = new Blob(["Hello, World!"], { type: "text/plain" });
let text = await blob.text();
console.log(text); // "Hello, World!"
If the Blob contains data in a non-UTF-8 encoding, use arrayBuffer() and then TextDecoder with the appropriate encoding:
// Blob containing Windows-1251 encoded text
let win1251Blob = new Blob([new Uint8Array([207, 240, 232, 226, 229, 242])]);
// blob.text() assumes UTF-8 (wrong result)
let wrongText = await win1251Blob.text();
console.log(wrongText); // "Ïðèâåò" (garbled)
// Correct approach: decode with the right encoding
let buffer = await win1251Blob.arrayBuffer();
let decoder = new TextDecoder("windows-1251");
let correctText = decoder.decode(buffer);
console.log(correctText); // "Привет"
blob.bytes(): Blob to Uint8Array
A newer method that returns a Promise resolving to a Uint8Array directly, without going through ArrayBuffer:
let blob = new Blob(["Hello"]);
let bytes = await blob.bytes();
console.log(bytes); // Uint8Array [72, 101, 108, 108, 111]
console.log(bytes instanceof Uint8Array); // true
blob.bytes() is a relatively new addition. If you need to support older browsers, use blob.arrayBuffer() and then wrap it in a Uint8Array:
// Fallback for browsers without blob.bytes()
async function blobToBytes(blob) {
if (blob.bytes) {
return blob.bytes();
}
let buffer = await blob.arrayBuffer();
return new Uint8Array(buffer);
}
Blob to Base64 (Data URL)
There is no direct blob.toBase64() method. To get a base64-encoded Data URL, use FileReader:
function blobToBase64(blob) {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(blob); // Result: "data:type;base64,..."
});
}
// Usage
let blob = new Blob(["Hello, World!"], { type: "text/plain" });
let dataUrl = await blobToBase64(blob);
console.log(dataUrl);
// "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=="
If you need just the raw base64 string (without the data: prefix):
async function blobToRawBase64(blob) {
let dataUrl = await blobToBase64(blob);
// Remove the "data:type;base64," prefix
return dataUrl.split(",")[1];
}
let base64 = await blobToRawBase64(blob);
console.log(base64); // "SGVsbG8sIFdvcmxkIQ=="
Base64 to Blob
Going the other direction (base64 string back to a Blob):
function base64ToBlob(base64, mimeType = "") {
let binaryString = atob(base64);
let bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return new Blob([bytes], { type: mimeType });
}
// Usage
let blob = base64ToBlob("SGVsbG8sIFdvcmxkIQ==", "text/plain");
let text = await blob.text();
console.log(text); // "Hello, World!"
// Useful for data URLs received from APIs
function dataUrlToBlob(dataUrl) {
let [header, base64] = dataUrl.split(",");
let mimeType = header.match(/data:([^;]+)/)?.[1] || "";
return base64ToBlob(base64, mimeType);
}
let imageBlob = dataUrlToBlob("data:image/png;base64,iVBORw0KGgo...");
ArrayBuffer to Blob
let buffer = new ArrayBuffer(4);
new Uint8Array(buffer).set([0x89, 0x50, 0x4E, 0x47]); // PNG header
let blob = new Blob([buffer], { type: "image/png" });
console.log(blob.size); // 4
console.log(blob.type); // "image/png"
Conversion Summary
┌─────────────┐
.text() │ │ new Blob([string])
◄──────────── │ Blob │ ◄──────────────────
(Promise) │ │
│ │ new Blob([buffer])
.arrayBuffer() │ │ ◄──────────────────
◄──────────── │ │
(Promise) │ │ new Blob([uint8arr])
│ │ ◄──────────────────
.bytes() │ │
◄──────────── │ │ new Blob([blob1, blob2])
(Promise) │ │ ◄──────────────────
│ │
.stream() │ │ .slice(start, end)
◄──────────── │ │ ──────────────────►
(ReadableStream) │ │ (new Blob)
└─────────────┘
FileReader URL.createObjectURL()
◄──────────── ──────────────────►
(base64/text/ (blob: URL)
arraybuffer)
blob.stream(): Readable Streams
The stream() method returns a ReadableStream that lets you read the Blob's content in chunks. This is ideal for processing large Blobs without loading the entire content into memory at once.
Basic Usage
let blob = new Blob(["Hello, World! This is a longer piece of text for streaming."]);
let stream = blob.stream();
let reader = stream.getReader();
while (true) {
let { done, value } = await reader.read();
if (done) {
console.log("Stream complete");
break;
}
// value is a Uint8Array chunk
console.log("Chunk:", value);
console.log("Chunk size:", value.length, "bytes");
}
Processing a Large File in Chunks
async function processLargeBlob(blob) {
let stream = blob.stream();
let reader = stream.getReader();
let decoder = new TextDecoder();
let totalBytes = 0;
let lineCount = 0;
while (true) {
let { done, value } = await reader.read();
if (done) break;
totalBytes += value.length;
let text = decoder.decode(value, { stream: true });
lineCount += (text.match(/\n/g) || []).length;
let progress = ((totalBytes / blob.size) * 100).toFixed(1);
console.log(`Progress: ${progress}%`);
}
// Flush remaining decoded text
decoder.decode();
console.log(`Total: ${totalBytes} bytes, ${lineCount} lines`);
}
Streaming a Blob Through a Transform
You can pipe a Blob's stream through transform streams for on-the-fly processing:
// Decompress a gzipped blob
async function decompressBlob(compressedBlob) {
let stream = compressedBlob.stream();
let decompressedStream = stream.pipeThrough(new DecompressionStream("gzip"));
// Collect the decompressed data into a new Blob
let response = new Response(decompressedStream);
return response.blob();
}
// Compress a blob
async function compressBlob(blob) {
let stream = blob.stream();
let compressedStream = stream.pipeThrough(new CompressionStream("gzip"));
let response = new Response(compressedStream);
return response.blob();
}
// Usage
let original = new Blob(["Hello, World! ".repeat(1000)], { type: "text/plain" });
console.log("Original size:", original.size); // ~14000 bytes
let compressed = await compressBlob(original);
console.log("Compressed size:", compressed.size); // Much smaller
let decompressed = await decompressBlob(compressed);
let text = await decompressed.text();
console.log("Decompressed:", text.substring(0, 50)); // "Hello, World! Hello, World! ..."
console.log("Decompressed size:", decompressed.size); // Back to ~14000
Computing a Hash of a Large Blob
async function hashBlob(blob, algorithm = "SHA-256") {
let buffer = await blob.arrayBuffer();
let hashBuffer = await crypto.subtle.digest(algorithm, buffer);
let hashArray = new Uint8Array(hashBuffer);
return Array.from(hashArray)
.map(b => b.toString(16).padStart(2, "0"))
.join("");
}
// Usage
let blob = new Blob(["Hello, World!"]);
let hash = await hashBlob(blob);
console.log("SHA-256:", hash);
// "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"
Practical Examples
Building a File Download Generator
class FileGenerator {
static text(content, filename) {
let blob = new Blob([content], { type: "text/plain" });
FileGenerator._download(blob, filename);
}
static json(data, filename) {
let content = JSON.stringify(data, null, 2);
let blob = new Blob([content], { type: "application/json" });
FileGenerator._download(blob, filename);
}
static csv(headers, rows, filename) {
let lines = [headers.join(",")];
for (let row of rows) {
let escapedRow = row.map(cell => {
let str = String(cell);
// Escape cells containing commas, quotes, or newlines
if (str.includes(",") || str.includes('"') || str.includes("\n")) {
return `"${str.replace(/"/g, '""')}"`;
}
return str;
});
lines.push(escapedRow.join(","));
}
// Add UTF-8 BOM for Excel compatibility
let bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
let csvContent = lines.join("\n") + "\n";
let blob = new Blob([bom, csvContent], { type: "text/csv;charset=utf-8" });
FileGenerator._download(blob, filename);
}
static html(content, filename) {
let fullHtml = `<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>${filename}</title></head>
<body>${content}</body></html>`;
let blob = new Blob([fullHtml], { type: "text/html" });
FileGenerator._download(blob, filename);
}
static _download(blob, filename) {
let url = URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
setTimeout(() => URL.revokeObjectURL(url), 5000);
}
}
// Usage
FileGenerator.text("Hello, World!", "greeting.txt");
FileGenerator.json(
{ users: [{ name: "Alice" }, { name: "Bob" }] },
"users.json"
);
FileGenerator.csv(
["Name", "Email", "Age"],
[
["Alice", "alice@example.com", 30],
["Bob", "bob@example.com", 25],
["Charlie", "charlie@example.com", 35]
],
"contacts.csv"
);
Image Processing Pipeline
// Load image → process on canvas → export as Blob → display/download
async function processImage(file) {
// 1. Create a Blob URL and load the image
let url = URL.createObjectURL(file);
let img = await loadImage(url);
URL.revokeObjectURL(url);
// 2. Draw on canvas and apply processing
let canvas = document.createElement("canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
let ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// Apply grayscale filter
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
let pixels = imageData.data;
for (let i = 0; i < pixels.length; i += 4) {
let gray = pixels[i] * 0.299 + pixels[i + 1] * 0.587 + pixels[i + 2] * 0.114;
pixels[i] = pixels[i + 1] = pixels[i + 2] = gray;
}
ctx.putImageData(imageData, 0, 0);
// 3. Export as Blob
let processedBlob = await new Promise(resolve => {
canvas.toBlob(resolve, "image/jpeg", 0.85);
});
return processedBlob;
}
function loadImage(src) {
return new Promise((resolve, reject) => {
let img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
// Usage
let fileInput = document.getElementById("imageInput");
fileInput.addEventListener("change", async (event) => {
let file = event.target.files[0];
if (!file) return;
console.log(`Original: ${(file.size / 1024).toFixed(1)} KB`);
let processed = await processImage(file);
console.log(`Processed: ${(processed.size / 1024).toFixed(1)} KB`);
// Display the processed image
let img = document.createElement("img");
let url = URL.createObjectURL(processed);
img.src = url;
img.onload = () => URL.revokeObjectURL(url);
img.style.maxWidth = "100%";
document.body.append(img);
});
Clipboard Integration
// Copy an image Blob to the clipboard
async function copyImageToClipboard(blob) {
try {
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
]);
console.log("Image copied to clipboard!");
} catch (error) {
console.error("Failed to copy:", error);
}
}
// Read an image from the clipboard
async function pasteImageFromClipboard() {
try {
let items = await navigator.clipboard.read();
for (let item of items) {
for (let type of item.types) {
if (type.startsWith("image/")) {
let blob = await item.getType(type);
console.log(`Pasted image: ${blob.type}, ${blob.size} bytes`);
return blob;
}
}
}
console.log("No image found in clipboard");
return null;
} catch (error) {
console.error("Failed to read clipboard:", error);
return null;
}
}
Sending Blobs with Fetch
// Upload a Blob directly
async function uploadBlob(blob, url) {
let response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": blob.type
},
body: blob // Blob can be used directly as fetch body
});
return response.json();
}
// Upload as FormData (multipart)
async function uploadFile(blob, filename, url) {
let formData = new FormData();
formData.append("file", blob, filename);
formData.append("description", "Uploaded via JavaScript");
let response = await fetch(url, {
method: "POST",
body: formData // No Content-Type header needed: browser sets it with boundary
});
return response.json();
}
// Receive a Blob from fetch
async function downloadImage(url) {
let response = await fetch(url);
let blob = await response.blob(); // Read response as Blob
console.log(`Downloaded: ${blob.type}, ${blob.size} bytes`);
return blob;
}
Generating a Blob from Canvas with Options
// Export canvas as different formats and qualities
async function canvasToBlob(canvas, format = "image/png", quality = 0.92) {
return new Promise((resolve) => {
canvas.toBlob(resolve, format, quality);
});
}
// Compare different export formats
async function compareFormats(canvas) {
let png = await canvasToBlob(canvas, "image/png");
let jpegHigh = await canvasToBlob(canvas, "image/jpeg", 0.95);
let jpegMed = await canvasToBlob(canvas, "image/jpeg", 0.75);
let jpegLow = await canvasToBlob(canvas, "image/jpeg", 0.50);
let webp = await canvasToBlob(canvas, "image/webp", 0.80);
console.log("PNG: ", (png.size / 1024).toFixed(1), "KB");
console.log("JPEG (95%): ", (jpegHigh.size / 1024).toFixed(1), "KB");
console.log("JPEG (75%): ", (jpegMed.size / 1024).toFixed(1), "KB");
console.log("JPEG (50%): ", (jpegLow.size / 1024).toFixed(1), "KB");
console.log("WebP (80%): ", (webp.size / 1024).toFixed(1), "KB");
return { png, jpegHigh, jpegMed, jpegLow, webp };
}
Summary
Blob is JavaScript's high-level interface for immutable binary data, designed for transporting data between your code and the outside world.
What a Blob Is:
- Immutable raw data with an optional MIME type.
- Cannot be read directly. Must be converted via
text(),arrayBuffer(),bytes(),stream(), orFileReader. - The
Fileobject extendsBlob, addingnameandlastModified.
Creating Blobs:
new Blob([parts], { type })creates a Blob from strings, ArrayBuffers, typed arrays, or other Blobs.blob.slice(start, end, type)creates a new Blob from a portion of an existing one (no copy).canvas.toBlob(callback, format, quality)exports canvas content as a Blob.
Blob URLs:
URL.createObjectURL(blob)creates a temporary URL (blob:origin/uuid) usable assrc,href, etc.URL.revokeObjectURL(url)frees the reference. Always revoke when done to prevent memory leaks.- Revoke after the resource is consumed (after
onload, not before).
Converting Blob Contents:
blob.text()returns a Promise resolving to a UTF-8 string.blob.arrayBuffer()returns a Promise resolving to anArrayBuffer.blob.bytes()returns a Promise resolving to aUint8Array.blob.stream()returns aReadableStreamfor chunked processing.- Use
FileReader.readAsDataURL(blob)for base64 Data URLs.
Common Use Cases:
- File downloads: Create a Blob, generate a URL, trigger download via
<a>. - File uploads: Send Blob directly as
fetchbody or viaFormData. - Image previews:
URL.createObjectURL(file)for instant<img>display. - Canvas export:
canvas.toBlob()for processed image output. - Clipboard:
ClipboardItemaccepts and returns Blobs. - Chunked uploads:
blob.slice()to split large files into uploadable pieces. - Compression: Pipe
blob.stream()throughCompressionStream/DecompressionStream.