Lekce 14 - Časovače a animace v JavaScriptu

JavaScript Základní konstrukce Časovače a animace v JavaScriptu

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

Vítám vás u další lekce našeho on-line kurzu JavaScriptu. V minulé lekci, 2D kontext plátna v JavaScriptu, jsme se věnovali 2D kontextu plátna. V tomto JavaScript tutoriálu se podíváme jak můžeme různé věci časovat a na to navážeme efektem animace.

Časovače

Časovače můžeme v JavaScriptu obsluhovat pomocí dvou funkcí.

setInterval() a setTimeout()

Funkce setInterval() a setTimeout() přijímají dva parametry. Funkci, která se bude volat, a časový interval (jak často nebo za jak dlouho se bude volat). Časové intervaly se udávají v milisekundách (1 sekunda = 1000 milisekund). Rozdíl mezi těmito dvěma funkcemi je vcelku zásadní. Zatímco setInterval() bude metodu volat každých X milisekund, setTimeout() ji zavolá pouze jednou jedinkrát a to za X milisekund.

Příklad

Cílem bude vytvořit text, který se bude postupně vypisovat. Nejprve bude vidět písmeno "A", o sekundu později přiskočí "h" a budou pokračovat třeba písmena "o", "j", "s", "v", "ě", "t", "e". Jakmile budou vypsána všechna písmena, text se vymaže a začne to celé znovu.

Nyní nebudeme mít již v HTML žádný element, vytvoříme jej až v JavaScriptu (implementace je jen na nás, ale pro zopakování DOMu necháme <body> prázdné :) ).

Do proměnné text uložíme text, který se bude vypisovat, a vytvoříme element <p>. Protože i <body> ještě nemusí být načtené (pokud importujeme skript v <head>), element <p> přidáme do elementu <body> až po načtení stránky.

let text = "Ahoj světe";
let element = document.createElement("p");
window.onload = function () {
        document.body.appendChild(element);
}

Nyní přejdeme k samotné funkci, která bude měnit text v našem elementu.

function zmenText() {

}

V této funkci si nejprve ověříme, zdali jsme již nevypsali všechna písmena. Pokud bychom totiž získali další písmeno v pořadí (když jsme na konci a žádné další písmeno neexistuje), dostali bychom chybu. Pokud se zadání (proměnná text) již shoduje s obsahem elementu, změníme hodnotu elementu na prázdný textový řetězec, aby se mohl text začít vypisovat znovu. Připravíme si i další větev, kam budeme psát další kód.

if (element.textContent == text) {
        element.textContent = "";
} else {
}

Dále si vezmeme písmeno (znak), které přidáme k textu elementu. Již víme, že length obsahuje délku řetězce. Vezmeme tedy další znak v pořadí tak, že změříme délku řetězce, který je vypsaný na obrazovku, a přičteme k ní 1. Jenže to má háček. Zatímco délka řetězce se počítá od 1 (1 znak = délka 1), tak znak na určité pozici (index) se počítá od nuly (1. znak = index 0). Proto musíme ještě jedničku odečíst, nakonec se +1 a -1 vynuluje, takže vezmeme znak na indexu aktuální délky textu vypsaného na obrazovce.

let pismenoKpridani = text[element.textContent.length];

A písmeno přidáme k textu zobrazeném v elementu.

element.textContent += pismenoKpridani;

Nyní již nezbývá než jen spustit interval. Do obsluhy události onload přidejme volání metody setInterval(), které jako první parametr předáme název metody zmenText (bez závorek) a jako druhý číslo 1000, aby byl interval změny textu přesně 1 sekundu.

setInterval(zmenText, 1000);

Interval se spustí až po jedné sekundě. Do té doby se bude aplikace jevit jako že 1 sekundu nereaguje, proto předtím ještě spustíme jednou metodu zmenText() ručně:

zmenText();
setInterval(zmenText, 1000);

Aplikaci si ještě můžete vylepšit, protože u mezery se aplikace bude jevit jako že se zasekla.

Vypisující se text
localhost

Složitější animace

Jistojistě by bylo hezké mít na webu nějakou složitější animaci. Ty jednoduché lze vyřešit v CSS, ale ty složitější již musíme řešit JavaScriptem. Celá pointa animací je, že v nějakém intervalu ovlivňujeme vlastnosti animovaného objektu.

Vezmeme si například podzimní výzdobu webu. Naprogramujeme skript, který nechá padat listí odshora dolů. Obrázky listí budeme mít v kořenovém elementu <body> a každý obrázek bude mít data-atribut data-podzim. Cíleně budeme vybírat pouze tyto obrázky, protože ne webu mohou (a bývají) i jiné obrázky a ty nechceme ovlivňovat.

Stáhněte si obrázek listu níže a vložte jej do nového projektu s následujícím HTML obsahem:

List

<img src="list.jpg" data-podzim />
<img src="list.jpg" data-podzim />
<img src="list.jpg" data-podzim />
<img src="list.jpg" data-podzim />
<img src="list.jpg" data-podzim />

Do projektu si přidejme CSS styl, ve kterém nastavíme obrázkům absolutní pozici (opět budeme nastavovat jen našim podzimním) a <body> nastylujme tak, aby nezobrazovalo scrollbary.

body > img[data-podzim] {
        position:absolute;
}

body {
        overflow:hidden;
}

Nadefinujme si proměnou listy, kam si po načtení stránky uložíme obrázky listů s data atributem data-podzim.

let listy;
window.onload = function () {
        listy = document.querySelectorAll("body > img[data-podzim]");
}

Listy projdeme cyklem:

for (let i = 0; i < listy.length; i++)

Budeme jim chtít nastavit výchozí pozici, která bude zleva pětina šířky okna pro každý (viz dále, tak aby se přesně rozprostřely) a shora mínus jejich výška (tak aby při načtení stránky začali sjíždět z horní hrany).

Velikost okna

Občas (jako např. teď) je potřebujeme pracovat s velikostí okna. Existuje několik způsobů a vlastností, které s touto velikostí souvisejí.

Skutečná velikost obrazovky

Úplně tu nejskutečnější velikost obrazovky vám řeknou vlastnosti width a height na objektu screen.

Oblast obrazovky vyhrazena aplikacím

Jedná se o velikost, kterou mohou použít aplikace. Jinými slovy v podstatě o velikost obrazovky výše mínus velikost systémových panelů (např. taskbaru). Vlastnosti najdeme opět na objektu screen jako availWidth a availHeight.

Velikost okna webové stránky

Nakonec si necháme tu nejdůležitější. Velikost plochy, kterou může zabírat naše aplikace. Vlastnosti najdeme tentokrát na objektu window a to jako innerWidth a innerHeight.

Nastavování CSS vlastností

Zatím nám ještě chybí jedna podstatná informace a to jak se nastavují vlastnosti CSS elementům DOMu.

Všechny elementy DOMu mají vlastnost style, která obsahuje vlastnosti pojmenované jako CSS vlastnosti. Ty se nezapisují pomlčkovou notací, ale notací camelCase (první slovo celé malými písmeny, každé další má počáteční písmeno velké, nejsou zde žádné mezery). Takže například:

document.body.style.backgroundColor = "red";

nastaví barvu pozadí elementu <body> na červenou.

Vraťme se k našemu padajícímu listí. Nastavme tedy pozice. U nastavování hodnot CSS je častá chyba zapomenutí jednotky:

listy[i].style.left = i * window.innerWidth / listy.length + "px";
listy[i].style.top = -listy[i].height + "px";

Posun

Nyní se vrhneme na funkci posun(), která posune všechny listy dolů. Funkce cyklem projde všechny listy a nastaví jim novou pozici. Tu získá na základě současné pozice listu (kterou musíme kvůli odstranění CSS jednotky z hodnoty naparsovat) a následného přičtení nějaké rozumné hodnoty, aby animace nebyla ani moc rychlá ani moc pomalá. Hodnotu si můžete zkusit upravit (hodnota 2 v kódu).

function posun() {
        for (let i = 0; i < listy.length; i++) {
                let novaPozice = parseInt(listy[i].style.top) + 2;
                listy[i].style.top = novaPozice + "px";
        }
}

Ještě je třeba ošetřit případ, kdy list vyjede z okna. V takovém případě musíme pozici znovu nastavit na mínus výšku obrázku. Dodejme podmínku mezi předchozí 2 řádky, abychom nemuseli CSS hodnotu nastavovat 2×.

if (novaPozice > window.innerHeight) {
        novaPozice = -listy[i].height;
}

Nakonec tedy v obsluze události načtení okna nastavme interval na nějakou rozumnou hodnotu (opět vyzkoušejte).

setInterval(posun, 20);

Protože postup byl komplexnější, uveďme si pro kontrolu kompletní zdrojový kód skriptu:

// Načtení stránky
window.onload = function () {
        listy = document.querySelectorAll("body > img[data-podzim]");

        // nastavení počáteční pozice
        for (let i = 0; i < listy.length; i++) {
                listy[i].style.left = i * window.innerWidth / listy.length + "px";
                listy[i].style.top = -listy[i].height + "px";
        }

        setInterval(posun, 20);
}

// Funkce pro časovač
function posun() {
        for (let i = 0; i < listy.length; i++) {
                let novaPozice = parseInt(listy[i].style.top) + 2;
                if (novaPozice > window.innerHeight) {
                        novaPozice = -listy[i].height;
                }
                listy[i].style.top = novaPozice + "px";
        }
}

Aplikaci spusťte. Uvidíte, že listí bude padat shora dolů a po vytečení z obrazovky zase odznovu.

Padající listí
localhost

Gratuluji, vaše první JavaScriptová animace je na světě. Můžete si ji samozřejmě vylepšit, aby byla ještě zajímavější.

Animace na plátně

V podobném duchu se nesou animace na plátně, kde v určitém intervalu celé plátno vymažeme a znovu vykreslíme a tak stále dokola. Jako ukázku si můžeme naprogramovat kolo štěstí. Pro jednoduchost si jej načteme ze statického obrázku. Vytvořme si tedy stránku s plátnem a to si načtěme v JavaScriptu. Do stránky si přidejme i obrázek s id="kolo".

Stáhněte si obrázek níže a spolu s následujícím HTML kódem jej vložte do nového projektu:

Kolo štěstí

<img src="kolo.png" id="kolo" />
<canvas id="platno" width="500" height="500"></canvas>

V skriptu si tyto objekty načtěme a přidejme si rovnou i proměnnou, kde budeme mít uložený úhel otočení. Obrázek nezapomeneme ze stránky skrýt.

let platno;
let kontext;
let otoceni = 0;
let obrazek;

window.onload = function () {
        platno = document.getElementById("platno");
        kontext = platno.getContext("2d");
        obrazek = document.getElementById("kolo");
        obrazek.style.display = "none";
}

V metodě prekresli() poté jednoduše vymažeme plátno a znovu na něj kolo vykreslíme. Nejprve vhodně přesuneme a otočíme kontext a poté obrázek vykreslíme. Nakonec přidáme k otočení jeden stupeň a to rovnou v jednotkách radiánů, ať se vyhneme přepočítávání. Víme že 360° = 2 * Pí, takže jeden stupeň je (2 * Pí) / 360.

function prekresli() {
        kontext.clearRect(0,0,500,500);
        kontext.save();
        kontext.translate(250, 250);
        kontext.rotate(otoceni);
        kontext.drawImage(obrazek, -225, -225);
        kontext.restore();
        otoceni += (2 * Math.PI) / 360; // otočení o 1 stupeň, ale v radiánech
}

V události načtení stránky opět nastavíme interval a vykreslíme kolo hned na začátku, abychom nemuseli čekat na dovršení prvního intervalu.

setInterval(prekresli, 20);
prekresli();

Výsledek:

Kolo štěstí
localhost

Gratuluji, již byste měli zvládat práci s animacemi. Dokázat si to můžete na cvičení.

Správně řešené animace

Všechna naše řešení animací sice fungují, ale podívejme se na ně z hlediska výkonu. Když uživatel opustí naší záložku prohlížeče, animace stále běží a vytěžuje procesor. Horší je to na mobilních zařízeních, kde to jde i poznat. Jak se animace dají řešit, aby za nás webový prohlížeč vyřešil animaci skutečně jen když je potřeba, si řekneme v příští lekci, JS requestAnimationFrame - Za lepší vykreslování.


 

Stáhnout

Staženo 405x (218.69 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript

 

 

Článek pro vás napsal Michal Žůrek (misaz)
Avatar
Jak se ti líbí článek?
6 hlasů
Autor se věnuje tvorbě aplikací pro počítače, mobilní telefony, mikroprocesory a tvorbě webových stránek a webových aplikací. Nejraději programuje ve Visual Basicu a TypeScript. Ovládá HTML, CSS, JavaScript, TypeScript, C# a Visual Basic.
Miniatura
Předchozí článek
2D kontext plátna v JavaScriptu
Miniatura
Všechny články v sekci
Základní konstrukce jazyka JavaScript
Miniatura
Následující článek
Cvičení k 14. lekci JavaScriptu
Aktivity (10)

 

 

Komentáře

Avatar
00
Člen
Avatar
00:15.5.2015 15:06
setIntervat()

?

 
Odpovědět 15.5.2015 15:06
Avatar
Odpovídá na 00
Michal Žůrek (misaz):16.5.2015 11:22

má tam být setInterval. Díky, opraveno.

Odpovědět 16.5.2015 11:22
Nesnáším {}, proto se jim vyhýbám.
Avatar
Jurajs
Člen
Avatar
Odpovídá na Michal Žůrek (misaz)
Jurajs:24.11.2015 14:27

Ahoj, kde jsi sebral v té podmínce textContent ?? Díky předem za vysvětlení :)

if (element.textContent == text) {
        element.textContent = "";
        return;
}
 
Odpovědět 24.11.2015 14:27
Avatar
David Koníček:18.12.2017 14:06

Projel jsem celý tento seriál a myslím, že by potřeboval trochu štábní kultury. Podle mě tam je dost věcí, které si musí lidi domýšlet a ne každého to třeba napadne. Všechny příklady co tu jsou si snažím sám přepisovat podle toho jak to čtu a u některých jsem trochu tápal a musel stáhnout přiložené kódy. Dále jestliže má být toto učební pomůcka, tak bych sjednotil jestli budeš psát středníky nebo ne a velikosti prvních písmen u funkcí.
Ale jinak mi seriál určitě hodně věcí osvětlil a byl mi přínosem ;)
P.S. cvika super ;)

Editováno 18.12.2017 14:08
Odpovědět 18.12.2017 14:06
Věř, běž a dokážeš!
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na David Koníček
David Čápka:31. ledna 20:58

Jsme rádi, že se líbí :) Právě děláme aktualizaci kurzu, aby byla jednotná syntaxe a přidali jsme živé ukázky k příkladům.

Odpovědět  +1 31. ledna 20:58
Jsem moc rád, že jsi na síti, a přeji ti top IT kariéru, ať jako zaměstnanec nebo podnikatel. Máš na to! :)
Avatar
Fero M
Člen
Avatar
Fero M:Včera 22:49

Olouvám se za blbý dotaz, ale jak bych mohl tuto animaci(listy) vložit do pozadí....Aby mi nepřekrývala obsah stránky?

 
Odpovědět Včera 22:49
Avatar
Odpovídá na Fero M
Michal Žůrek (misaz):Včera 23:08

musí se v css nastavit pozice na absolute a z-index na nějaký třeba i záporný.

Odpovědět Včera 23:08
Nesnáším {}, proto se jim vyhýbám.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 7 zpráv z 7.