Přejít k navigační liště

Zdroják » Různé » Upload obrázků pomocí HTML5

Upload obrázků pomocí HTML5

HTML5 přináší několik zajímavých API. Při použití v kombinaci s elementem <canvas> je možné vytvořit naprosto úžasný formulář k nahrávání obrázků. V tomto článku si ukážeme jak. Popsaná API fungují dobře pro Firefox 4 i pro prohlížeče postavené na Webkitu. Bohužel pro IE bude třeba použít klasický formulář.

Článek vychází z anglického originálu How to develop a HTML5 image uploader, jehož autorem je Paul Rouget a který vyšel na stránkách Mozilla Hacks pod licencí CC-BY-SA. Pod stejnou licencí je k dispozici i tento překlad.

Získávání obrázků

Drag and Drop

K zadávání souborů pro upload slouží element <input type="file">. Mnohem lepší je umožnit uživatelům obrázky prostě přetáhnout přímo na vaši webovou stránku.

Autor napsal podrobný článek o implementaci drag-and-drop na vašich webových stránkách. Můžete se také podívat na tutoriál drag-and-drop od Mozilly.

Vícenásobný výběr

Můžete uživatelům povolit vybrat víc souborů najednou pomocí konstrukce

<input type="file" multiple>

Více o tomto postupu naleznete v článku o vícenásobném výběru souborů od téhož autora.

Zpracování souborů

Použití File API

(Detaily naleznete v dokumentaci k File API.) 

Nejprve si zjistíme seznam souborů vložených pomocí <input> elementu nebo prostým přetažením:

// pomocí input elementu
var filesToUpload = input.files;
// pomocí drag-and-drop
function onDrop(e) {
  filesToUpload = e.dataTransfer.files;
}

Ověříme, zda se skutečně jedná o obrázky:

if (!file.type.match(/image.*/)) {
  // tento soubor není obrázek
};

Zobrazení náhledů

Máme dvě možnosti. Buď použijeme FileReader (z File API), nebo novější metodu createObjectURL() .

createObjectURL()

var img = document.createElement("img");
img.src = window.URL.createObjectURL(file);

FileReader

var img = document.createElement("img");
var reader = new FileReader();
reader.onload = function(e) {img.src = e.target.result}
reader.readAsDataURL(file);

Použití canvasu

Jakmile máme surová data obrázku v <img> elementu, můžeme jej před dalším zpracováním vykreslit do elementu <canvas>.

var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);

Zmenšení obrázku

Někteří lidé nahrávají obrázky přímo z fotoaparátu. Rozlišeních takových obrázků, nehledě na jejich velikost, může být pro dané účely zbytečně vysoké. Pro změnu velikosti existuje velice jednoduchý trik. Obrázek stačí vykreslit do menšího canvasu (např. 800×600). Nesmíte ovšem zapomenout na správný poměr stran.

var MAX_WIDTH = 800;
var MAX_HEIGHT = 600;
var width = img.width;
var height = img.height;

if (width > height) {
  if (width > MAX_WIDTH) {
    height *= MAX_WIDTH / width;
    width = MAX_WIDTH;
  }
} else {
  if (height > MAX_HEIGHT) {
    width *= MAX_HEIGHT / height;
    height = MAX_HEIGHT;
  }
}
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);

Úpravy obrázku

Teď, když máme obrázek v canvasu, jsou možnosti dalších úprav v podstatě neomezené. Můžeme například aplikovat filtr sépie:

var imgData = ctx.createImageData(width, height);
var data = imgData.data;
var pixels = ctx.getImageData(0, 0, width, height);
for (var i = 0, ii = pixels.data.length; i < ii; i += 4) {
    var r = pixels.data[i + 0];
    var g =pixels.data[i + 1];
    var b = this.pixels.data[i + 2];
    data[i + 0] = (r * .393) + (g *.769) + (b * .189);
    data[i + 1] = (r * .349) + (g *.686) + (b * .168)
    data[i + 2] = (r * .272) + (g *.534) + (b * .131)
    data[i + 3] = 255;
}
ctx.putImageData(imgData, 0, 0);

Odeslání na server pomocí XMLHttpRequest

Jakmile máme obrázky od uživatele načtené a zpracované, je potřeba je poslat na server.

Jak poslat canvas

Opět máme dvě možnosti. Můžeme převést canvas na datové URL, nebo (ve Firefoxu) vytvořit z canvasu  soubor.

canvas.toDataURL()

var dataurl = canvas.toDataURL("image/png");

Převod canvasu na soubor

var file = canvas.mozGetAsFile("foo.png");

Atomické nahrávání souborů

Nechte uživatele poslat soubor nebo více souborů najednou.

Průběh náhrávání souborů na server

Použijeme událostí na objektu uploadu ke zobrazení průběhu.

xhr.upload.addEventListener("progress", function(e) {
  if (e.lengthComputable) {
    var percentage = Math.round((e.loaded * 100) / e.total);
    // úprava průběhu
}, false);

Použití FormData

Samotné odeslání dat můžeme jednoduše provést pomocí xhr.send(file). Může se ovšem stát, že budete chtít k obrázkům přidat nějaké další informace (např. jméno, klíč apod.).

V takovém případě vytvořte zprávu typu multipart/form-data pomocíFormData objektu. (Viz Firefox 4: jednodušší JS zpracování formulářů pomocí FormData.)

var fd = new FormData();
fd.append("name", "paul");
fd.append("image", canvas.mozGetAsFile("foo.png"));
fd.append("key", "××××××××××××");
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://your.api.com/upload.json");
xhr.send(fd);

Otevřené API

Možná budete chtít zpřístupnit svou službu i ostatním.Na co je důležité nezapomenout?

Přístup z jiných domén

Ve standardním nastavení je vaše API dostupné pouze z vaší domény. Chcete-li jej zpřístupnit i ostatním, musíte to povolit v HTTP hlavičce:

Access-Control-Allow-Origin: *

Případně přístup omezit na několik vybraných domén.

Přečtěte si taky Cross-Origin Resource Sharing nebo článek Cross Site XHR zde na Zdrojáku.

postMessage

(Díky Danielu Goodwinovi za tento tip.)

Můžete naslouchat zprávám poslaným skrz postMessage . Můžete tak lidem umožnit použít vaše API skrz postMessage:

document.addEventListener("message", function(e){
    // parametery pomocí e.data
    var key = e.data.key;
    var name = e.data.name;
    var dataurl = e.data.dataurl;
    // upload
}
// jakmile je vše odesláno, přesměrujte uživatele pomocí postMessage na původní URL

A to je vše, přátelé. Podělte se o další tipy v komentářích.

Komentáře

Subscribe
Upozornit na
guest
25 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Jakub Vrána

Místo file.type.mat­ch(/image.*/) by asi bylo lepší použít /^image//.tes­t(file.type), jinak se náhodou může strefit něco úplně jiného.

rooobertek

aj pre operu <input type=“file“ multiple=“multiple“ max=“99″>
Číslo je nepodstatné, nehrá žiadnu úlohu

fos4

Školy nemám :-) ale neměla by být událost „message“ zavěšená na window místo window.document ?

chleba

Tenhle clanek uz tady jednou byl. Drag&Drop se stejnejma ukazkama spolecne jeste s ajaxovym prikladem pro upload. Suchy.

Michal Wiglasz

Nevybavuju si, že by se v něm psalo o zmenšení obrázku přes canvas (namísto na straně serveru) a zobrazení průběhu nahrávání.

Martin Malý

Máte na mysli pravděpodobně tento článek. Za těch deset měsíců se technologie poněkud posunula, v článku se řeší trochu jiná věc trochu jiným způsobem… Ano, téma a příklad jsou podobné, a co má být?

enumag

Ahoj, článek byl velmi zajímavý. Už jsem drag&drop upload dělal, ale bez canvasu. Bohužel mi z článku poněkud ušlo k čemu je ten canvas dobrý, když mohu obrázky poslat přes XHR rovnou. Je to čistě k tomu, kdybych chtěl před uploadem aplikovat nějaké vlastní filtry? Nějak si nedokážu představit, kdy je to žádoucí.

Villlém

Canvas je tam hlavně k vůli tomu, aby jste mohl uživateli ukázat náhled obrázků, které vybral.

Mordae

Verim, ze to vetsi servery potesi. Urychli se tak nahravani. Uzivatel predem uvidi, co vlastne uvidi. Nebude treba mit tolik kapacity na resizing obrazku…

Srigi

K tomu ale ziadny Canvas netreba. Staci normalne ten URLblob natrepat do src=““.

enumag

Přesně tak. Což mě vrací k původní otázce: K čemu ten canvas?

pkroh

Editace před nahráním na server?

enumag

Nějaký rozumný (reálný) případ, kdy se to hodí?

Mordae

Resize? Vyrez obliceje kontaktu z fotky? Nebo mi neco unika?

enumag

Je dobré když na serveru uchováš obrázek v původní velikosti ze kterého pak generuješ různě velké miniatury. Před uploadem to nemá smysl.

Pokud jde o obličeje tak by ti je buď musel uživatel nějak označit anebo javascript rozpoznat – ani jedno není nijak triviální.

Takže ano, uniká.

josefrichter

Bože tyhle chytráky, co mají hned jasno, že je to na pytel, fakt žeru. Představ si třeba blbej inzertní server, kam můžeš uploadnout fotky prodávanýho zboží. Velmi pravděpodobně tam budeš chtít omezit velikost fotek třeba na 300x300px. Nepřijde ti rozummnější udělat to hned na client side, než pokaždé tahat třeba deset 8MPx fotek a nedej bože je ukládat? Jo, můžeš uživateli napsat „nahrávejte fotky velikosti do 300×300, formát jpg, maximálně 50kB“ – ale to není zrovna „klientský přístup.“ Už?

enumag

Ne nepřijde. tedy alespoň ne do doby, kdy bude canvas podporovaný ve všech prohlížečích.

Michal Wiglasz

Tak to je jednoduché – kde to jde, zmenšíme obrázek u klienta (přes canvas), kde ne, tak holt se bude muset zapotit linka a server. I tak se to docela vyplatí.

Plus nevím, ve kterém aktuálním prohlížeči to nejde – pro IE existuje exCanvas.

enumag

Tedy spíše canvas a drag&drop upload.

dasim

Díky za článek, tohle je spíš taková obecná připomínka. Uvažovali jste na Zdrojáku nad přidáním živých ukázek toho, co v podobných článcích vytváříte? Vždycky, když někde vidím návod na implementaci něčeho a spoustu zdrojových kódů, první co hledám je tlačítko „Demo“. V zahraničních magazínech je většinou k nalezení.

Já si samozřejmě přečtu základní popis a výklady u kusů kódu, ale mnohem radši se do pročítání článku pustím až potom, co vidím co přesně mi z něj vypadne. Nemluvě o užitečnosti funkční ukázky pro další využití. Podobné ukázky mi chyběly i např. u některých dílů série o HTML5 (např. popis drag-n-drop).

Jasně, že to není vždy zapotřebí a ani nutnost, pokud sem přijdu s tím, že „přesně tohle teď potřebuju, tak si to rovnou kopírováním zdrojáků naimplementuju!“, jen je to občas fajn. Díky.

(Pokud v článcích někde ukázky odkázané jsou, tak se omlouvám, ale já je prostě nenašel.)

antaresin

Souhlasím s kolegou…taky bych podobnou „funkci“ velmi uvítal! Každopádně děkuji za podobné články.

Martin Malý

Obecná připomínka je dobrá a k věci, a máte pravdu, že by se ukázky hodily. U některých článků je máme – zrovna v tom o drag and drop je cca v půlce titulek „Ukázka“ a hned pod tím „Ukažme si jednoduché demo“. Dokonce i u článků o Node.js nebo jiných serverových technologiích připravujeme celé demonstrační virtuální servery. Z vlastní zkušenosti ale mohu říct, že vytvořit takové demo zabere leckdy víc času než napsat článek – a to je podstata problému třeba u takovýchto překladů. Nemohu po překladateli chtít, aby demo napsal, a já sám nemusím mít vždy čas něco připravit, jako se to stalo tentokrát.

Ale souhlasím s vámi v tom, že by bylo vhodné mít u těchto článků hned někde po ruce „tlačítko DEMO“, a popřemýšlím, jak jej tam v rámci redakčního systému vhodně zapracovat. Díky za tip!

korCZis

Naprosty souhlas! Proc root.cz nema neco jako „Google Code Playground“ ?

renergy
manakmichal

potýkám se s problémem u Chrome 10.0.648.205, kde FileAPI na lokálním počítači nevrací nic, avšak ve FF 3.6 ano. Oficiální ukázka na mozilla.org funguje i v Chrome. Jejich kód jsem si tedy zkopíroval a stejně to vrací prázdné elementy, nějaký bug nebo omezující featura?

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.