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