This commit is contained in:
Ilya Kantor 2019-03-20 15:09:06 +03:00
parent 31f83a5e1b
commit 4ffd79e337
65 changed files with 1481 additions and 28000 deletions

View file

@ -4,7 +4,7 @@
In the browser, there are additional higher-level objects, described in [File API](https://www.w3.org/TR/FileAPI/).
`Blob` consists of an optional string `type` (a MIME-type usually), plus `blobParts` this a sequence of other `Blob` objects, strings and `BufferSources`.
`Blob` consists of an optional string `type` (a MIME-type usually), plus `blobParts` -- a sequence of other `Blob` objects, strings and `BufferSources`.
![](blob.png)
@ -16,12 +16,28 @@ The constructor syntax is:
new Blob(blobParts, options);
```
- **`blobParts`** is an array of Blob/BufferSource/String values.
- **`blobParts`** is an array of `Blob`/`BufferSource`/`String` values.
- **`options`** optional object:
- **`type`** -- blob type, usually MIME-type, e.g. `image/png`,
- **`endings`** -- whether to transform end-of-line `\r\n` to `\n` end vise versa, to make the blob correspond to current OS newlines. By default `"transparent"` (do nothing), but also can be `"native"` (transform).
- **`endings`** -- whether to transform end-of-line to make the blob correspond to current OS newlines (`\r\n` or `\n`). By default `"transparent"` (do nothing), but also can be `"native"` (transform).
Then we can access blob as a whole, or extract slices with:
For example:
```js
// create Blob from a string
let blob = new Blob(["<html></html>"], {type: 'text/html'});
// please note: the first argument must be an array [...]
```
```js
// create Blob from a typed array and strings
let hello = new Uint8Array([72, 101, 108, 108, 111]); // "hello" in binary form
let blob = new Blob([hello, ' ', 'world'], {type: 'text/plain'});
```
We can extract blob slices with:
```js
blob.slice([byteStart], [byteEnd], [contentType]);
@ -33,11 +49,15 @@ blob.slice([byteStart], [byteEnd], [contentType]);
The arguments are similar to `array.slice`, negative numbers are allowed too.
We can't change data directly in a blob, but we can copy parts of blobs, create new blobs from them, mix them and so on. This behavior is similar to Javascript strings: we can't change a character in a string, but we can make a new corrected string.
```smart header="Blobs are immutable"
We can't change data directly in a blob, but we can slice parts of blobs, create new blobs from them, mix them into a new blob and so on.
This behavior is similar to Javascript strings: we can't change a character in a string, but we can make a new corrected string.
```
## Blob as URL
A Blob can be easily used in an URL for `<a>`, `<img>` or other tags.
A Blob can be easily used as an URL for `<a>`, `<img>` or other tags, to show its contents.
Let's start with a simple example. By clicking on a link you download a dynamically-generated blob with `hello world` contents as a file:
@ -46,18 +66,15 @@ Let's start with a simple example. By clicking on a link you download a dynamica
<a download="hello.txt" href='#' id="link">Download</a>
<script>
let binary = new Uint8Array([72, 101, 108, 108, 111]); // "hello" in binary form
// create blob of multiple parts
let blob = new Blob([binary, ' ', 'world'], {type: 'text/plain'});
let blob = new Blob(["Hello, world!"], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
</script>
```
We can also create a link in Javascript and simulate a click by `link.click()`, then download starts authomatically.
We can also create a link dynamically in Javascript and simulate a click by `link.click()`, then download starts authomatically.
Here's the similar "on the fly" blob creation and download code, now without HTML:
Here's the similar "on the fly" blob creation and download code, but without HTML:
```js run
let link = document.createElement('a');
@ -80,19 +97,19 @@ That's what the generated url looks like:
blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273
```
The browser keeps an internal url -> blob mapping. For each url generated by `URL.createObjectURL`, it stores a reference to the corresponding blob.
The browser for each url generated by `URL.createObjectURL` stores an the url -> blob mapping internally. So such urls are short, but allow to access the blob.
A generated url is only valid while the current document is open. And it allows to reference the blob in `<img>`, `<a>`, any other object that expects an URL.
A generated url is only valid while the current document is open. And it allows to reference the blob in `<img>`, `<a>`, any other object that expects an url.
There's a side-effect though. While there's an mapping for a blob, the blob itself resides in the memory. The browser can't free it.
The mapping is automatically cleared on document unload, so blobs are freed then. But if an app is long-living, then that doesn't happen. So if we create an URL, that blob will hang in memory, even if not needed any more.
The mapping is automatically cleared on document unload, so blobs are freed then. But if an app is long-living, then that doesn't happen soon. So if we create an URL, that blob will hang in memory, even if not needed any more.
**`URL.revokeObjectURL(url)` removes the reference from the internal mapping, thus allowing the blob to be deleted (if there are no other references), and the memory to be freed.**
In the last example, we intend the blob to be used only once, for instant downloading, so we call `URL.revokeObjectURL(link.href)` immediately.
In the previous example though, with the clickable HTML-link, we dont't call `URL.revokeObjectURL(link.href)`, because that would make the blob url invalid. After the revocation, as the mapping is removed, the url doesn't work any more.
In the previous example though, with the clickable HTML-link, we don't call `URL.revokeObjectURL(link.href)`, because that would make the blob url invalid. After the revocation, as the mapping is removed, the url doesn't work any more.
## Blob to base64
@ -111,7 +128,7 @@ For instance, here's a smiley:
The browser will decode the string and show the image: <img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">
To transform a blob into base64 and vise-versa, we'll use built-in `FileReader/FileWriter` objects. In the [next chapter](info:file) we'll cover those more in-detail. For now, it's important only that they can read a blob as data url, that we can assign it to `link.href`.
To transform a blob into base64, we'll use the built-in `FileReader` object. It can read data from Blobs in multiple formats. In the [next chapter](info:file) we'll cover it more in-depth.
Here's the demo of downloading a blob, now via base-64:
@ -123,16 +140,13 @@ let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
*!*
let reader = new FileReader();
reader.readAsDataURL(blob); // converts the blob to base64 and calls onload
*/!*
reader.onload = function() {
link.href = reader.result; // data url
link.click();
};
*!*
reader.readAsDataURL(blob); // converts the blob to base64 and calls onload
*/!*
```
Both ways of making an URL of a blob are usable. But usually `URL.createObjectURL(blob)` is simpler and faster.
@ -144,57 +158,70 @@ Both ways of making an URL of a blob are usable. But usually `URL.createObjectUR
- Performance and memory losses on big blobs for encoding.
```
## Image or a page to blob
## Image to blob
We can create a blob of an image, or even a page screenshot.
We can create a blob of an image, an image part, or even make a page screenshot. That's handy to upload it somewhere.
That's usually done via `<canvas>` element:
Image operations are done via `<canvas>` element:
1. We draw an existing image on canvas using [canvas.drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage).
2. Canvas [.toBlob(callback, format, quality)](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) creates a blob and calls `callback` with it.
1. Draw an image (or its part) on canvas using [canvas.drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage).
2. Call canvas method [.toBlob(callback, format, quality)](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) that creates a blob and runs `callback` with it when done.
In the example below, an image is just copied, but we could cut from it, or transform it on canvas prior to making a blob:
```js run
let img = document.querySelector('img'); // just any image
// take any image
let img = document.querySelector('img');
// make <canvas> of the same size
let canvas = document.createElement('canvas');
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;
let context = canvas.getContext('2d');
context.drawImage(img, 0, 0); // allows to cut image
// we can context.rotate() as well, and do many other things
// copy image to it (this method allows to cut image)
context.drawImage(img, 0, 0);
// we can context.rotate(), and do many other things on canvas
// toBlob is async opereation, callback is called when done
canvas.toBlob(function(blob) {
// download the blob
// blob ready, download it
let link = document.createElement('a');
link.download = 'example.png';
link.href = URL.createObjectURL(blob);
link.click();
// delete the internal blob reference, to let the browser clear memory from it
URL.revokeObjectURL(link.href);
}, 'image/png');
```
For screenshotting a page, we can use a library such as <https://github.com/niklasvh/html2canvas> to draw the screenshot on `<canvas>`, and then download it the same way.
If we prefer `async/await` instead of callbacks:
```js
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
```
For screenshotting a page, we can use a library such as <https://github.com/niklasvh/html2canvas>. What it does is just walks the page and draws it on `<canvas>`. Then we can get a blob of it the same way as above.
## From Blob to ArrayBuffer
The `Blob` constructor allows to create a blob from almost anything.
The `Blob` constructor allows to create a blob from almost anything, including any `BufferSource`.
But we can also revert back to lowest-level `ArrayBuffer` using `FileReader`:
But if we need to perform low-level processing, we can get the lowest-level `ArrayBuffer` from it using `FileReader`:
```js
// get arrayBuffer from blob
let fileReader = new FileReader();
fileReader.onload = function(event) {
let arrayBuffer = fileReader.result;
};
*!*
fileReader.readAsArrayBuffer(blob);
*/!*
fileReader.onload = function(event) {
let arrayBuffer = fileReader.result;
};
```
@ -204,9 +231,9 @@ While `ArrayBuffer`, `Uint8Array` and other `BufferSource` are "binary data", a
That makes Blobs convenient for upload/download operations, that are so common in the browser.
Methods that perform web-requests, such as [XMLHttpRequest](info:xmlhttprequest), [fetch](info:fetch) and so on, can work with `Blob` natively, as well as with other binary types.
Methods that perform web-requests, such as [XMLHttpRequest](info:xmlhttprequest), [fetch](info:fetch-basics) and so on, can work with `Blob` natively, as well as with other binary types.
That said, it's still possible to convert betweeen `Blob` and low-level binary data types:
We can easily convert betweeen `Blob` and low-level binary data types:
- We can make a Blob from a typed array using `new Blob(...)` constructor.
- We can get back `ArrayBuffer` from a Blob using `FileReader`, and then create a view over it for low-level binary processing.