jQuery effects - quick start

V minulém článku, ve kterém jsem se zabýval JavaScript Closures, jsem se zmiňoval o tom, že mě k jejich studiu donutilo používání efektů z knihovny jQuery. Také jsem sliboval, že o svých zkušenostech něco málo napíšu v dalším článku. Nuže směle do toho.

jQuery je obecná knihovna obalující odlišné implementace (více než odlišnosti jazyka, míním odlišnosti práce s DOM reprezentací) JavaScriptu v běžně používaných prohlížečích. Efekty jsou pouze její minoritní částí, kterou možná většina vývojářů pracujících s jQuery ani nevyužívá. Jelikož jsem hračička, koketoval jsem s efekty už od první chvíle, kdy jsem s jQuery začal. Z globálního pohledu musím říct, že mě překvapuje, že tyto efekty fungují velmi dobře skrze všechny podporované prohlížeče a kupodivu jsou poměrně svižné i na pomalejších počítačích (pomalejšími mám na mysli, průměrný počítač koupený před 3-4 lety). Základní použití je velmi jednoduché a zvládne ho i člověk, který s JavaScriptem a jQuery teprve začíná. Kromě základních efektů dodávaných přímo jako součást jQuery Core (show, hide, toggle, fadeIn, fadeOut, animate), je k dispozici ještě oficiální dodatečná knihovna s widgety a dalšími effekty známá jako jQuery UI (zde najdete řadu dalších pěkných efektů, které byly kdysi součástí js knihovny interface.js).

Pokud máte zájem o to shlédnout některé z animací, které jsme pro naše zákazníky vytvořili, zde nabízím pár odkazů:

  • Moje Firma - web Komerční Banky zaměřený na podnikatelskou sféru

    • v části podnikatelská poradna jsme využili efekty k odeslání formuláře s daty na server bez reloadu celé stránky, efekt si můžete bez následků vyzkoušet pokud se pokusíte odeslat prázdný formulář - výsledkem bude animace, která vám zobrazí chyby, které ve skutečnosti generuje server v odpovědi na odeslání formuláře (žádné JS kontroly ;-) )
      Update 29.10.2010 Odkaz již není funkční
  • TROTINA AUTO, s.r.o. - stránky prodejce ojetých vozidel ve Východních Čechách

    • vyzkoušejte si třeba vložit pár vozidel na parkoviště, vybrat ve vyhledávání terénní vozidla, v detailu vozidla odeslat doporučení příteli apod.
    • kapitolou samou o sobě je potom Dražba na ruby, kterou si můžete vyzkoušet je v případě, že je nějaká aktuálně vypsaná - zde se veškerá logika aplikace odehrává v podstatě jen na dvou obrazovkách a zbytek je řešen AJAXem s využitím jQuery efektů


Základní použití

Jak už jsem avizoval základní použití efektů je velmi jednoduché. Pokud si vezmeme jako příklad jednoduché skrytí / zobrazení konkrétní části stránky (div, span, p atp.), stačí nám toto:


function example1() {
	$("#example1do").hide();
	$("#example1undo").show("fast");
}

Spuštění příkladu vyvolá pozvolné skrytí prvního tlačítka s příkladem a pomalé zobrazení tlačítka druhého. První část (dolar zastupuje volání jQuery knihovny, výraz v prvních závorkách CSS selektor) vrátí list DOM objektů (obalených v jQuery reprezentaci, takže je možné dál volat metody jQuery), na kterých se zavolá metoda v druhé části výrazu (např. hide()). Většina animací umožňuje definovat rychlost buď jako přesný časový interval v milisekundách (např. hide(10000) bude znamenat úplné skrytí prvku v 10 vteřinách) nebo ve třech stupních "slow", "normal", "fast".

Trošku specifickým efektem je funkce toggle(), která interně provede zavolání show nebo hide funkce v závislosti na aktuálním stavu daného prvku (tzn. pokud je prvek skrytý zavolá se show, pokud je prvek zobrazený, zavolá se hide). Na první pohled prkotina, která ve výsledku docela dost urychluje práci. Základní toggle pracuje s metodami show a hide, nicméně i některé další efekty nabízejí "toggle" alternativu, která vám šetří práci s analýzou aktuálního stavu zobrazení daných prvků. Krátký příklad:


function example2() {
	$(".example2").toggle(10000);
}


Jak je z příkladu hezky vidět, jedním příkazem zvládneme část stránky skrýt a jinou část zobrazit - stačí nám k tomu shodná třída "example2", kterou můžeme použít v selektoru.

Řetězení efektů / paralelní, sériové spouštění

Vynucené sériové spouštění operací

Pokud chceme začít dělat s efekty divočejší kousky, měli bychom být schopni pracovat s načasováním. Příklady, které jsem doposud uváděl provádějí tzv. paralelní vykonání efektů - efekty se spustí současně a v závislosti s nastavenou délkou trvání odpovídajícím způsobem skončí (pokud všem nastavíme stejné časování např. 1 vteřinu, měly by také v jeden moment skončit, viz. předchozí příklad). Často ale potřebujeme nastavit nějaký animační scénář, který by měl jednotlivé efekty zřetězit. Ukažme si to na příkladu - mějme nějaký blok textu, který chceme v konkrétní chvíli vyměnit. Můžeme to hulvátsky provést tak, že rovnou vyměníme text daného paragrafu, nicméně to není zrovna způsob atraktivní pro uživatele (může to vypadat ošklivě nebo si toho uživatel nemusí všimnout). Proto vše obalíme do jednoduché animace - pozvolna paragraf skryjeme, ve skrytém stavu vyměníme texty a opětovně paragraf pozvolna zobrazíme. K tomu budeme ale muset umět použít sériové spuštění efektů - viz. příklad:


function example3() {
	$("#example3paragraph").hide("slow", function() {
		$(this).load("/text.txt", function(data) {
			$(this).show("slow");
		});
	});
}

Tento text bude nahrazen textem nahraným ze serveru.

V příkladu navíc pracujeme i s Ajaxovou funkcí "load", kterou poskytuje jQuery, abychom si načetli nějaký text ze serveru. Jak vidíte, relativně složité chování lze napsat na 4 řádky kódu.

Co je na příkladu důležité je to, že většina (ne-li všechny) jQuery animací umožňuje jako poslední parametr předat Closure, která bude spuštěna po dokončení dané animace. Tzn. v našem případě bude výměna textu pomocí metody "load" zavolána až ve chvíli, kdy skončí animace skrytí prvku a animace jeho opětovného zobrazení nezačne dřív, než se podaří načíst a vyměnit text paragrafu. Řetězení animací není nijak omezeno, stejně tak jako není omezeno hnízdění (nestování) Closures.

Z tohoto důvodu je nutné znát alespoň základy pro práci s JavaScript Closures. Pokud chcete navíc extenzivněji pracovat pomocí AJAXu se serverem (ne tak jednoduchým způsobem, jak jsme si ukázali na příkladě, ale např. pomocí knihovny DWR volat metody na serveru), budete pravděpodobně potřebovat předávat si ve volání i řadu dat, což už vyžaduje trošku větší jistotu v otázce Closures. To jsem se však pokusil alespoň trochu nastínit v minulém článku.

Sériové spouštění animací

Autoři jQuery chtěli definici sériového spouštění animací zjednodušit a proto zavedli tzv. fronty (queue) animací. Nevím přesně jak jsou fronty implementované, ale představuji si, že uvnitř je asociativní mapa, kde klíčem je konkrétní DOM element a hodnotou právě fronta animací.

Paradoxně se zavedením front řada věcí o trochu zkomplikovala. Člověk, který si hned neuvědomí, jak to uvnitř jQuery funguje, se může velmi jednoduše zamotat. Celá záležitost je o to problematičtější tím, že pracujeme s paralelními operacemi v reálném čase. Jedinou možností, kterou máme abychom zjistili, co se kdy děje, je, nastavit dlouhé periody animací a zírat na zpomalenou sekvenci nebo použít nějaký mechanismus logování s časovými známkami.

V prvním příkladě jsem ukazoval paralelní spuštění animací, pokud se však skript bude týkat stejného DOM elementu nebudou animace spuštěny paralelně, ale sériově - jedna po druhé. O to se právě stará animační fronta. Zkuste porovnat jednotlivé zápisy v následující funkci:


function testAnimations() {
	//ukázka z prvního příkladu
	//bude spuštěno paralelně - jedná se o odlišné DOM elementy
	$("#example1do").hide();
	$("#example1undo").show("fast");
	//pokud se ale to samé bude týkat stejného DOM elementu
	//jako je na tomto příkladě, bude spuštění sériové
	$("#example1do").show();
	$("#example1do").hide();
	//celé to jde zapsat díky tomuto chování ještě jednodušeji
	$("#example1do").show().hide();
}

Díky tomuto chování můžeme v kondenzované formě (viz. poslední řádek) zapsat animační sekvence týkající se stejných DOM elementů na stránce. Pokud chceme dělat složitější animační scénáře, kde potřebujeme pracovat s více DOM elementy na stránce, nezbyde nám jiná varianta než použití callback metod (Closures).

S animačními frontami si můžeme taky ošklivě naběhnout, pokud definujeme hromadné animační scénáře (tzn. kdy nám selektor vybere více než jeden DOM element). Fronta platí totiž jen pro konkrétní element a nikoliv pro použitý selektor. Situaci si můžeme znázornit na následujícím příkladě:

První div
Druhý div
Třetí div
Čtvrtý div

function example4_good() {
	$(".blok").css("position", "absolute");
	$("#d1").animate({ top: "+=80px"}, 2000);
	$("#d2").animate({ top: "+=110px"}, 2000);
	$("#d3").animate({ top: "+=140px"}, 2000);
	$("#d4").animate({ top: "+=170px"}, 2000);
	$("#d1").animate({ fontSize: "150%"}, 2000);
	$("#d3").animate({ fontSize: "150%"}, 2000);
	$(".blok").animate({ top: "-=50px"}, 2000);
}

Jak je vidět, na začátku metody instruujeme DIVy 1 - 4, aby se posunuly vertikálně dolů o různý počet pixelů. Jelikož se tyto instrukce týkají pokaždé jiného elementu, budou probíhat paralelně. V další části instruujeme již jen DIVy 1 a 3, aby se jejich font postupně o polovinu zvětšil (zvětšování fontů se opět bude provádět paralelně, ovšem díky frontě až po dokončení vertikálního posunu DIVů 1 a 3 dolů). V poslední části pak hromadným selektorem instruujeme najednou všechny DIVy 1 - 4 (všechny mají totiž class="blok"), aby se posunuly vertikálně nahoru o 50px. V poslední instrukci ale tkví ten problém - díky použití animačních front se totiž načasování animace rozloží takto:

Vteřina 1. 3. 5.
DIV #d1 posun dolů zvětšení fontu posun nahoru
DIV #d2 posun dolů posun nahoru nic
DIV #d3 posun dolů zvětšení fontu posun nahoru
DIV #d4 posun dolů posun nahoru nic

Aby nebyl celé legrace konec, animační fronty se týkají pouze animací. Tzn. když mezi jednotlivé animace vložíte nějakou neanimační operaci - jako třeba v následujícím příkladě AJAXové volání serveru, tato operace se vám do fronty nevloží a provede se paralelně se začátkem animace:


function example4_bad() {
	$("#example4paragraph").hide(10000).load("/text.txt").show(10000);
}

Tento text bude nahrazen textem nahraným ze serveru.

Záměrně jsem prodloužil délku trvání animace na 10 vteřin, abyste mohli postřehnout, že již při skrývání paragrafu se v něm provede náhrada textu. Tedy nikoliv, až ve chvíli kdy je paragraf skrytý, jak bychom původně mohli čekat. Samozřejmě díky tomu, že se jedná o AJAXové volání, záleží na rychlosti odezvy serveru - někdy by se mohlo zdát, že se animace chová tak jak zamýšlíme, ale opak je pravdou - může se chovat jen o náhodnou synchronizaci díky pomalé odpovědi ze strany serveru (tím že prodloužíme dobu animace to můžeme jednoduše odhalit).

Síla funkce animate

Většina (pokud ne všechny) jQuery efekty používají na pozadí generickou funkci s názvem Animate. Ta umožňuje rozfázovat přechod od jednoho nastavení CSS atributů k jinému. Vezměme si jednoduchý příklad - při implementaci dražby vozidel pro TROTINA AUTO jsme chtěli při sledování seznamu dražených vozidel průběžně aktualizovat stavy vozidel a aktuálně vydražená vozidla odlišovat jiným barevným podkladem řádku. Vše se děje ajaxovým způsobem, tzn. bez reloadu stránky se mají postupně jednotlivá vozidla podbarvovat tak jak jsou aktuálně prodávána. Pokud uživatel sleduje danou stránku nevypadalo by hezky pokud bychom provedli změnu barevného podkladu skokově - pro tento případ jsme použili právě funkci Animate.

Efekt animate vypočte číselnou řadu mezi dvěma hodnotami tak, aby vlastní změna proběhla plynule v čase určeném animaci. Možnosti animate jsou omezené - nelze použít vždy a na vše. Všimněte si především této části dokumentace:

"Only properties that take numeric values are supported (e.g. backgroundColor is not supported). As of jQuery 1.2 you can now animate properties by em and % (where applicable). Additionally, in jQuery 1.2, you can now do relative animations - specifying a "+=" or "-=" in front of the property value moves the element positively or negatively, relative to its current position."

function example5() {
	if ($("body").css("opacity") != 1) {
		$("body").animate({ opacity: 1}, 2000, function() {$("#example5").attr("value", "Spustit příklad 5");});
	} else {
		$("body").animate({ opacity: 0.1}, 2000, function() {$("#example5").attr("value", "Vzít příklad 5 zpět");});
	}
}

Samozřejmě barvy jsou jedna z věcí, které bychom právě animovat chtěli. Pro tento účel ovšem musíme použít dodatečný plugin, který nám to umožní.

Funkce animate má kromě standardních parametrů určujících změnu cílové vlastnosti nebo použití transformační funkce (ano kromě změny CSS vlastností je možné přidávat pro animate vlastní složitější funkce) také sadu Options. V nich je možné dále zasahovat do chování této animace:

  • duration
    délka trvání v milisekundách
  • easing
    linear/swing - aproximační funkce pro výpočet změny vlastností v čase
  • complete
    closure, která se má vykonat po ukončení animace
  • step
    closure, která se má vykonat v každém kroku animace; má dva parametry: now - aktuální hodnotu měněné vlastnosti, fx - objekt reprezentující efekt
  • queue
    true/false - definuje zda má být animace zařazena do animační fronty elementu či nikoliv

Konkrétní efekty (show, hide, fadeIn, fadeOut ...) již tyto options nevystavují, takže pokud budete chtít dělat složitější věci budete muset používat tuto nízkoúrovňovou funkci.

Závěrem

Efekty jQuery jsou skvělý nástroj jak zatraktivnit statické HTML a také elegantní způsob jak vyřešit změnu obsahu částí stránky při použití AJAXu. Jejich použití je natolik triviální, že jsou přístupné takřka všem. Velmi brzy se však člověk osmělí a pustí se do složitějších scénářů, kde si bez hlubší znalosti principů uvnitř jQuery a JavaScript closures, může jednoduše naběhnout. Mluvím z vlastní zkušenosti, protože já jsem na tom pěkných pár hodin nechal.

Reference