S HTML5 přišla kromě jiného také rozšíření API pro práci s obrázky přímo v prohlížeči. Časy, kdy sadističtí programátoři vyhazovali hlášky při každé, byť napravitelné chybě, jsou téměř pryč. Opravdu nic nepotěší víc, než když na pomalém připojení po 2 minutách nahrávání dojde k chybě s tím, že obrázek je moc velký a nahráváme znova. Zmenši si sám.
HTML5 nám umožňuje obrázek zobrazit hned po jeho výběru z disku, umožňuje nám dělat jeho předběžné úpravy (výřez, překlopení) a při tvorbě galerií přijde vhod také možnost přetáhnout soubory do okna prohlížeče, což je známo jako Drag & Drop.
Obrázek můžeme zobrazit vcelku jednoduše odchycením události change na prvek formuláře pro výběr souboru. Získáme vybraný soubor, který načteme a zobrazíme. V případě, že uživatel po vybrání souboru klikne na nový výběr a klikne na zrušit, bude mít proměnná file v následující jednoduché ukázce hodnotu undefined.
$('input[type="file"]').on('change', function(event) { var file = this.files[0]; if(file !== undefined) { var input = this; var reader = new FileReader; reader.onloadend = function() { if(file.type.split('/')[0] === 'image') { var image = document.createElement('img'); image.src = reader.result; $(input).after(image); } else { alert("Vybraný soubor není platný obrázek."); } }; reader.readAsDataURL(file); } });
K otáčení využijeme CSS vlastnost transform. Budeme si ukládat postupné otáčení a při každé změně si do skrytého prvku formuláře nastavíme hodnotu otočení, kterou pak budeme potřebovat pro skutečné zpracování na serveru.
$('a.rotate').on('click', function(event) { var preview = $('img.preview'); if(preview.css('transform') === 'none') { $(preview).css('transform', 'rotate(90deg)').attr('rotated', 90); } else { var rotated = parseInt($(preview).attr('rotated')) + 90; if(rotated == 360) rotated = 0; $(preview).css('transform', 'rotate(' + rotated + 'deg)').attr('rotated', rotated); } $('input.rotate').val($(preview).attr('rotated')); });
K výběru na obrázku můžeme použít canvas – plátno, na které vykreslíme obrázek a navěšenými událostmi zajistíme možnost výběru myší. Velmi jednoduchou implementaci lze vidět v následující ukázce, v praxi by bylo vhodné samozřejmě vynutit určité proporce výběru, posun selekce a také změnu kurzoru při najetí na obrázek, nebo poznámku pod náhled, aby uživatel věděl, že má možnost s obrázkem něco dělat.
$('input[type="file"]').on('change', function(event) { var file = this.files[0]; if(file !== undefined) { var input = this; var reader = new FileReader; reader.onloadend = function() { if(file.type.split('/')[0] === 'image') { var image = new Image; image.src = reader.result; // Vytvoříme si plátno var canvas = document.createElement('canvas'); canvas.width = image.width; canvas.height = image.height; $(input).after(canvas); // Získáme kontext var context = canvas.getContext('2d'); // Nakreslíme obrázek context.drawImage(image, 0, 0); context.strokeStyle = '#F00'; // Barva výběru (červená) // Objekt s nastavením selekce var selection = {x: 0, y: 0, width: 0, height: 0}; // Navěsíme funkci pro selekci $(canvas) .on('mousedown', function(event) { // Nastavíme nulový bod selekce selection.x = event.pageX - $(this).position().left; selection.y = event.pageY - $(this).position().top; }) .on('mousemove', function(event) { if(event.which === 1) { // Stisknuto levé tlačítko myši var x = event.pageX - $(this).position().left; var y = event.pageY - $(this).position().top; // Nastavíme nové rozměry selekce selection.width = x - selection.x; selection.height = y - selection.y; // Překreslíme plátno context.drawImage(image, 0, 0); context.beginPath(); context.rect(selection.x, selection.y, selection.width, selection.height); context.stroke(); } }).on('mouseleave', function(event) { // Přepočítáme selekci x = selection.width >= 0 ? selection.x : selection.x + selection.width; y = selection.height >= 0 ? selection.y : selection.y + selection.height; width = Math.round(Math.abs(selection.width)); height = Math.round(Math.abs(selection.height)); // Předáme do formuláře $('input[name="selection"]').val('x=' + x + '&y=' + y '&width=' + width + '&height=' + height); }); } else { alert("Vyberte, prosím, obrázek."); } }; reader.readAsDataURL(file); } });
Kód do skrytého formulářového políčka nastaví souřadnice a rozměry výběru v podobě, ve které je lze na serveru parsovat například pomocí funkce parse_str. Je nutné si na straně serveru všechny vstupní údaje ohlídat a nepropustit cokoliv, co neodpovídá masce. To ale platí u validace v kombinaci s použitím Javascriptu obecně.
S HTML5 přišla také podpora Drag & Drop, což velice oceňuji. Nerad vybírám jednotlivé obrázky ze složky pomocí dialogu prohlížeče a je mi příjemnější přetáhnout obrázky z průzkumníku myší, občas je také užitečný fakt, že se dají do stránky přetáhnout obrázky z lišty stahovaných souborů (což mi připomíná – dejte uživatelům možnost nahrát svůj avatar ze vzdáleného serveru).
var formData = new FormData; $('form') .on('dragover', function(event) { event.preventDefault(); // Prvek přijímá soubory }) .on('drop', function(event) { event.preventDefault(); // Potlačíme výchozí chování prohlížeče for(i = 0; (file = event.originalEvent.dataTransfer.files[i]) !== undefined; i++) { if(file.type.split('/')[0] !== 'image') { continue; // Přeskočíme neobrázkové soubory } // Přidáme soubory k odeslání formData.append('image[' + i + ']', file); } }) .on('submit', function() { event.preventDefault(); // Budeme manipulovat s daty k odeslání var request = new XMLHttpRequest; request.open('POST', this.action); request.send(formData); });
Obrázky bychom samozřejmě mohli také vykreslit a dát možnost jejich editace, dá se toho dosáhnout prostou kombinací s výše uvedenými ukázkami. Tento způsob posílá obrázky na server asynchronně. Odesílání běžným způsobem se mi zdá problematické, navíc v případě hromadného uploadu je asi lepší odesílat právě AJAXem a řekl bych, že rozumné by bylo dělat to po jednotlivých obrázcích, klidně ve více vláknech. To z toho důvodu, že při případném přerušení spojení (např. při odpojení od WiFi a podobně) nepřijdeme o všechny nahrávané obrázky, protože server je v tomto případě schopen zpracovat pouze celý požadavek najednou. Také by bylo příjemné zobrazovat stav uploadu pro jednotlivé soubory, což není již problém docílit za použití Progress Events.
Žádné komentáře