ArrayBuffer
a pohledy jsou součástí standardu ECMA, součástí JavaScriptu.
V prohlížeči jsou další objekty vyšší úrovně popsané v File API, konkrétně Blob
.
Blob
sestává z volitelného řetězce type
(obvykle typ MIME) plus blobParts
– sekvence dalších Blob
objekty, řetězce a BufferSource
.
Syntaxe konstruktoru je:
new Blob(blobParts, options);
blobParts
je poleBlob
/BufferSource
/String
hodnoty.options
volitelný objekt:type
–Blob
typu, obvykle typu MIME, např.image/png
,endings
– zda transformovat konec řádku naBlob
odpovídají aktuálním novým řádkům OS (\r\n
nebo\n
). Ve výchozím nastavení"transparent"
(nedělat nic), ale také může být"native"
(transformace).
Například:
// create Blob from a string
let blob = new Blob(["<html>…</html>"], {type: 'text/html'});
// please note: the first argument must be an array [...]
// 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'});
Můžeme extrahovat Blob
plátky s:
blob.slice([byteStart], [byteEnd], [contentType]);
byteStart
– počáteční bajt, ve výchozím nastavení 0.byteEnd
– poslední bajt (výhradně, ve výchozím nastavení až do konce).contentType
–type
nového blob, ve výchozím nastavení stejný jako zdroj.
Argumenty jsou podobné array.slice
, jsou povolena i záporná čísla.
Blob
objekty jsou neměnné
Nemůžeme měnit data přímo v Blob
, ale můžeme rozdělit části Blob
, vytvořte nový Blob
objekty z nich, smíchejte je do nového Blob
a tak dále.
Toto chování je podobné řetězcům JavaScriptu:nemůžeme změnit znak v řetězci, ale můžeme vytvořit nový opravený řetězec.
Blob jako adresa URL
Blob lze snadno použít jako adresu URL pro <a>
, <img>
nebo jiné značky, aby se zobrazil její obsah.
Díky type
, můžeme také stáhnout/nahrát Blob
objektů a type
přirozeně se změní na Content-Type
v síťových požadavcích.
Začněme jednoduchým příkladem. Kliknutím na odkaz stáhnete dynamicky generovaný Blob
s hello world
obsah jako soubor:
<!-- download attribute forces the browser to download instead of navigating -->
<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>
Můžeme také vytvořit odkaz dynamicky v JavaScriptu a simulovat kliknutí pomocí link.click()
a stahování se spustí automaticky.
Zde je podobný kód, který způsobí, že si uživatel stáhne dynamicky vytvořený Blob
, bez jakéhokoli 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
trvá Blob
a vytvoří pro něj jedinečnou adresu URL ve tvaru blob:<origin>/<uuid>
.
To je hodnota link.href
vypadá takto:
blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273
Pro každou adresu URL vygenerovanou URL.createObjectURL
prohlížeč ukládá URL → Blob
mapování interně. Takže takové adresy URL jsou krátké, ale umožňují přístup k Blob
.
Vygenerovaná adresa URL (a tedy i odkaz na ni) je platná pouze v aktuálním dokumentu, dokud je otevřený. A umožňuje odkazovat na Blob
v <img>
, <a>
, v podstatě jakýkoli jiný objekt, který očekává URL.
Je tu však vedlejší účinek. Zatímco existuje mapování pro Blob
, Blob
sám sídlí v paměti. Prohlížeč jej nemůže uvolnit.
Mapování se při uvolnění dokumentu automaticky vymaže, takže Blob
objekty jsou pak osvobozeny. Ale pokud je aplikace dlouhodobá, pak se to nestane brzy.
Pokud tedy vytvoříme adresu URL, bude to Blob
zůstane v paměti, i když už nebude potřeba.
URL.revokeObjectURL(url)
odstraní odkaz z interního mapování, čímž povolí Blob
k vymazání (pokud neexistují žádné další odkazy) a paměť, která má být uvolněna.
V posledním příkladu máme na mysli Blob
použít pouze jednou, pro okamžité stažení, proto nazýváme URL.revokeObjectURL(link.href)
okamžitě.
V předchozím příkladu s odkazem HTML, na který lze kliknout, nevoláme URL.revokeObjectURL(link.href)
, protože by to znamenalo Blob
neplatná adresa URL. Po zrušení, když je mapování odstraněno, adresa URL již nefunguje.
Bloba na base64
Alternativa k URL.createObjectURL
je převést Blob
do řetězce zakódovaného v base64.
Toto kódování představuje binární data jako řetězec ultra bezpečných „čitelných“ znaků s kódy ASCII od 0 do 64. A co je důležitější – toto kódování můžeme použít v „data-urls“.
Datová adresa URL má tvar data:[<mediatype>][;base64],<data>
. Takové adresy URL můžeme použít všude, stejně jako „běžné“ adresy URL.
Zde je například smajlík:
<img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">
Prohlížeč dekóduje řetězec a zobrazí obrázek:
Chcete-li transformovat Blob
do base64, použijeme vestavěný FileReader
objekt. Dokáže číst data z objektů Blob v různých formátech. V další kapitole se tomu budeme věnovat podrobněji.
Zde je ukázka stahování blobu, nyní přes 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); // converts the blob to base64 and calls onload
reader.onload = function() {
link.href = reader.result; // data url
link.click();
};
Oba způsoby vytvoření adresy URL z Blob
jsou použitelné. Ale obvykle URL.createObjectURL(blob)
je jednodušší a rychlejší.
- Pokud nám záleží na paměti, musíme je odvolat.
- Přímý přístup k blob, žádné „kódování/dekódování“
- Není třeba nic odvolávat.
- Ztráty výkonu a paměti na velkých
Blob
objekty pro kódování.
Obrázek do blob
Můžeme vytvořit Blob
obrázku, části obrázku nebo dokonce vytvořit snímek stránky. Je užitečné to někam nahrát.
Operace s obrázky se provádějí pomocí <canvas>
prvek:
- Nakreslete obrázek (nebo jeho část) na plátno pomocí canvas.drawImage.
- Call canvas method .toBlob(callback, format, quality), která vytvoří
Blob
a spustícallback
s tím, až bude hotovo.
V níže uvedeném příkladu je obrázek pouze zkopírován, ale před vytvořením blob z něj můžeme vyjmout nebo transformovat na plátno:
// 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');
// 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 operation, callback is called when done
canvas.toBlob(function(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');
Pokud dáváme přednost async/await
místo zpětných volání:
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
Pro screenshot stránky můžeme použít knihovnu, jako je https://github.com/niklasvh/html2canvas. To, co dělá, je, že prochází stránku a kreslí ji na <canvas>
. Pak můžeme získat Blob
stejným způsobem jako výše.
Z blobu do ArrayBuffer
Blob
konstruktor umožňuje vytvořit blob z téměř čehokoli, včetně libovolného BufferSource
.
Pokud ale potřebujeme provést nízkoúrovňové zpracování, můžeme získat nejnižší úroveň ArrayBuffer
od blob.arrayBuffer()
:
// get arrayBuffer from blob
const bufferPromise = await blob.arrayBuffer();
// or
blob.arrayBuffer().then(buffer => /* process the ArrayBuffer */);
Z blobu do streamu
Když čteme a zapisujeme do blobu většího než 2 GB
, použití arrayBuffer
se pro nás stává náročnější na paměť. V tomto okamžiku můžeme objekt blob přímo převést na stream.
Proud je speciální objekt, který z něj umožňuje číst (nebo do něj zapisovat) po částech. Zde je to mimo náš rozsah, ale zde je příklad a více si můžete přečíst na https://developer.mozilla.org/en-US/docs/Web/API/Streams_API. Proudy jsou vhodné pro data, která jsou vhodná pro zpracování po jednotlivých kusech.
Blob
rozhraní stream()
metoda vrací ReadableStream
který po přečtení vrátí data obsažená v Blob
.
Pak z něj můžeme číst takto:
// 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);
}
Shrnutí
Zatímco ArrayBuffer
, Uint8Array
a další BufferSource
jsou „binární data“, objekt Blob představuje „binární data s typem“.
Díky tomu jsou objekty Blob vhodné pro operace nahrávání/stahování, které jsou v prohlížeči tak běžné.
Metody, které provádějí webové požadavky, jako je XMLHttpRequest, načítání a tak dále, mohou pracovat s Blob
nativně, stejně jako s jinými binárními typy.
Můžeme snadno převádět mezi Blob
a nízkoúrovňové binární datové typy:
- Můžeme vytvořit
Blob
z typovaného pole pomocínew Blob(...)
konstruktor. - Můžeme získat zpět
ArrayBuffer
z objektu Blob pomocíblob.arrayBuffer()
a poté nad ním vytvořte pohled pro nízkoúrovňové binární zpracování.
Konverzní streamy jsou velmi užitečné, když potřebujeme zpracovat velké blob. Můžete snadno vytvořit ReadableStream
z kapky. Blob
rozhraní stream()
metoda vrací ReadableStream
který po přečtení vrátí data obsažená v blobu.