ArrayBuffer
et les vues font partie de la norme ECMA, une partie de JavaScript.
Dans le navigateur, il existe d’autres objets de niveau supérieur, décrits dans File API, en particulier Blob
.
Blob
consiste en une chaîne de caractères optionnelle type
(un type de MIME habituellement), plus blobParts
– une séquence d’autres objets Blob
, chaînes de caractères et BufferSource
.
La syntaxe du constructeur est la suivante:
new Blob(blobParts, options);
blobParts
est un tableau deBlob
/BufferSource
/String
.options
objet optionnel:type
– le type duBlob
, généralement de type MIME, par exemple.image/png
,endings
– s’il faut transformer la fin de ligne pour rendre leBlob
correspondent aux nouvelles lignes de l’OS actuel (\r\n
ou\n
)."transparent"
Par défaut (ne fait rien), mais peut aussi être"native"
(transformer).
Par exemple:
// créer un Blob à partir d'une chaîne
let blob = new Blob(["<html>…</html>"], {type: 'text/html'});
// veuillez noter: le premier argument doit être un tableau [...]
// créer un objet blob à partir d'un tableau typés et de chaînes de caractères
let hello = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" sous forme binaire
let blob = new Blob([hello, ' ', 'world'], {type: 'text/plain'});
Nous pouvons extraire des parties du Blob
avec :
blob.slice([byteStart], [byteEnd], [contentType]);
byteStart
– l’octet de départ, par défaut 0.byteEnd
– le dernier octet (exclusif, par défaut jusqu’à la fin).contentType
– Letype
du nouvel objet blob, par défaut le même que la source.
Les arguments sont similaires à array.slice
, les nombres négatifs sont également autorisés.
Blob
sont immuablesNous ne pouvons pas modifier les données directement dans un Blob
, mais nous pouvons découper des parties d’un Blob
, créer de nouveaux objets Blob
à partir d’eux, les mélanger dans un nouveau Blob
et ainsi de suite.
Ce comportement est similaire aux chaînes de caractères JavaScript: nous ne pouvons pas changer un caractère dans une chaîne, mais nous pouvons créer une nouvelle chaîne corrigée.
Blob comme URL
Un Blob peut être facilement utilisé comme URL pour <a>
, <img>
ou d’autres balises, pour afficher son contenu.
Grâce au type
, nous pouvons également télécharger / uploader des objets Blob
, et le type
devient naturellement Content-Type
dans les requêtes réseau.
Commençons par un exemple simple. En cliquant sur un lien, vous téléchargez un Blob
généré dynamiquement avec le contenu de hello world
sous forme de fichier:
<!-- l'attribut de téléchargement force le navigateur à télécharger au lieu de naviguer -->
<a download="hello.txt" href='#' id="link">Download</a>
<script>
let blob = new Blob(["Hello, world!"], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
</script>
On peut aussi créer un lien dynamiquement en JavaScript et simuler un clic par link.click()
, puis le téléchargement démarre automatiquement.
Voici un code similaire qui oblige l’utilisateur à télécharger le Blob
créé dynamiquement, sans aucun HTML :
let link = document.createElement('a');
link.download = 'hello.txt';
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(link.href);
URL.createObjectURL
prend un Blob
et crée une URL unique pour celui-ci, sous la forme blob:<origin>/<uuid>
.
Voilà à quoi ressemble la valeur de link.href
:
blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273
Pour chaque URL générée par URL.createObjectURL
, le navigateur stocke en interne un mappage URL → Blob
. De telles URL sont donc courtes, mais permettent d’accéder au Blob
.
Une URL générée (et donc le lien avec elle) n’est valide que dans le document actuel, tant qu’il est ouvert. Et cela permet de référencer le Blob
dans <img>
, <a>
, essentiellement tout autre objet qui attend une URL.
Une URL générée (et donc le lien avec elle) n’est valide que dans le document actuel, tant qu’il est ouvert. Et cela permet de référencer le Blob
dans <img>
,<a>
, ou tout autre objet qui attend une URL.
Il y a cependant un effet secondaire. Bien qu’il y ait un mappage pour un Blob
, le Blob
lui-même réside dans la mémoire. Le navigateur ne peut pas le libérer.
Le mappage est automatiquement effacé lors du déchargement du document, les objets Blob
sont alors libérés. Mais si une application dure longtemps, cela ne se produit pas de sitôt.
Donc, si nous créons une URL, ce Blob
restera en mémoire, même s’il n’est plus nécessaire.
URL.revokeObjectURL(url)
supprime la référence du mappage interne, permettant ainsi de supprimer le Blob
(s’il n’y a pas d’autres références), et de libérer la mémoire.
Dans le dernier exemple, nous voulons que le Blob
ne soit utilisé qu’une seule fois, pour un téléchargement instantané, donc nous appelons URL.revokeObjectURL(link.href)
immédiatement.
Dans l’exemple précédent avec le lien HTML cliquable, nous n’appelons pas URL.revokeObjectURL(link.href)
, car cela rendrait l’url Blob
invalide. Après la révocation, comme le mappage est supprimé, l’URL ne fonctionne plus.
Blob en base64
Une alternative à URL.createObjectURL
est de convertir un Blob
en une chaîne de caractères encodée en base64.
Cet encodage représente des données binaires sous la forme d’une chaîne de caractères “lisibles” ultra-sûrs avec des codes ASCII de 0 à 64. Et ce qui est plus important – nous pouvons utiliser cet encodage dans “data-urls”.
Une URL de données a la forme data:[<mediatype>][;base64],<data>
. Nous pouvons utiliser de telles URL partout, au même titre que les URL “ordinaires”.
Par exemple, voici un smiley:
<img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">
Le navigateur décodera la chaîne de caractères et affichera l’image:
Pour transformer un Blob
en base64, nous utiliserons l’objet FileReader
intégré. Il peut lire les données des Blobs dans plusieurs formats. Dans le chapitre suivant nous le couvrirons plus en détail.
Voici la démo du téléchargement d’un blob, maintenant via base-64:
let link = document.createElement('a');
link.download = 'hello.txt';
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
let reader = new FileReader();
reader.readAsDataURL(blob); // convertit le blob en base64 et appelle onload
reader.onload = function() {
link.href = reader.result; // URL de données
link.click();
};
Les deux manières de créer une URL d’un Blob
sont utilisables. Mais généralement URL.createObjectURL(blob)
est plus simple et plus rapide.
- Nous devons les révoquer si nous nous soucions de la mémoire.
- Accès direct au blob, pas d’“encodage / décodage”
- Pas besoin de révoquer quoi que ce soit.
- Perte de performances et de mémoire sur les gros objets
Blob
pour l’encodage.
Image à blob
Nous pouvons créer un Blob
d’une image, une partie d’image, ou même faire une capture d’écran de page. C’est pratique pour le télécharger quelque part.
Les opérations sur les images se font via l’élément <canvas>
:
- Dessinez une image (ou sa partie) sur le canevas en utilisant canvas.drawImage.
- Appeler la méthode canvas .toBlob(callback, format, quality) qui crée un
Blob
et exécutecallback
avec lui une fois terminé.
Dans l’exemple ci-dessous, une image est simplement copiée, mais nous pourrions la couper ou la transformer sur un canevas avant de créer un blob :
// prendre n'importe quelle image
let img = document.querySelector('img');
// rendre <canvas> de la même taille
let canvas = document.createElement('canvas');
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;
let context = canvas.getContext('2d');
// copier l'image dessus (cette méthode permet de couper l'image)
context.drawImage(img, 0, 0);
// nous pouvons context.rotate(), et faire beaucoup d'autres choses sur canvas
// toBlob est une opération asynchrone, le callback est appelé lorsque c’est terminé
canvas.toBlob(function(blob) {
// blob prêt, téléchargez-le
let link = document.createElement('a');
link.download = 'example.png';
link.href = URL.createObjectURL(blob);
link.click();
// supprimer la référence blob interne, pour laisser le navigateur en effacer la mémoire
URL.revokeObjectURL(link.href);
}, 'image/png');
Si nous préférons async/await
au lieu de callbacks :
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
Pour faire une capture d’écran d’une page, nous pouvons utiliser une bibliothèque telle que https://github.com/niklasvh/html2canvas. Ce qu’elle fait, c’est simplement parcourir la page et la dessiner sur un <canvas>
. Ensuite, nous pouvons en obtenir un Blob
de la même manière que ci-dessus.
De Blob à ArrayBuffer
Le constructeur Blob
permet de créer un blob à partir de presque tout, y compris de n’importe quel BufferSource
.
Mais si nous devons effectuer un traitement de bas niveau, nous pouvons obtenir le ArrayBuffer
de plus bas niveau à partir de blob.arrayBuffer()
:
// obtenir arrayBuffer à partir de blob
const bufferPromise = await blob.arrayBuffer();
// or
blob.arrayBuffer().then(buffer => /* process the ArrayBuffer */);
From Blob to stream
When we read and write to a blob of more than 2 GB
, the use of arrayBuffer
becomes more memory intensive for us. At this point, we can directly convert the blob to a stream.
A stream is a special object that allows to read from it (or write into it) portion by portion. It’s outside of our scope here, but here’s an example, and you can read more at https://developer.mozilla.org/en-US/docs/Web/API/Streams_API. Streams are convenient for data that is suitable for processing piece-by-piece.
The Blob
interface’s stream()
method returns a ReadableStream
which upon reading returns the data contained within the Blob
.
Then we can read from it, like this:
// get readableStream from blob
const readableStream = blob.stream();
const stream = readableStream.getReader();
while (true) {
// for each iteration: value is the next blob fragment
let { done, value } = await stream.read();
if (done) {
// no more data in the stream
console.log('all blob processed.');
break;
}
// do something with the data portion we've just read from the blob
console.log(value);
}
Résumé
Alors qu’ArrayBuffer
, Uint8Array
et autres BufferSource
sont des “données binaires”, un Blob représente des “données binaires de type”.
Cela rend les Blobs pratiques pour les opérations de téléchargement (upload / download), qui sont courantes dans le navigateur.
Les méthodes qui effectuent des requêtes Web, telles que XMLHttpRequest, fetch et ainsi de suite, peuvent fonctionner avec Blob
de manière native, ainsi qu’avec d’autres types binaires.
Nous pouvons facilement convertir les types de données binaires Blob
et de bas niveau :
- Nous pouvons créer un
Blob
à partir d’un tableau typé en utilisant le constructeurnew Blob(...)
. - Nous pouvons récupérer
ArrayBuffer
à partir d’un Blob en utilisantblob.arrayBuffer()
, puis créer une vue dessus pour le traitement binaire de bas niveau.
Les flux de conversion sont très utiles lorsque nous devons gérer de gros blob. Vous pouvez facilement créer un ReadableStream
à partir d’un blob. La méthode stream()
de l’interface Blob
renvoie un ReadableStream
qui, lors de la lecture, renvoie les données contenues dans le blob.