IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 26 - Časovače a animace v JavaScriptu

V předešlém cvičení, Řešené úlohy k 23.-25. lekci JavaScriptu, jsme si procvičili nabyté zkušenosti z předchozích lekcí.

V tomto tutoriálu základů JavaScriptu si ukážeme, jak našim kresbám nastavit časovače a docílit tím efektu animace na webové stránce.

Časovače a animace

Časovače můžeme v JavaScriptu obsluhovat pomocí dvou funkcí, které si nyní představíme.

Funkce setInterval() a setTimeout()

Funkce setInterval() a setTimeout() přijímají dva parametry. Volanou funkci (bez závorek) a časový interval určující, jak často nebo za jak dlouho se bude tato funkce volat. Časové intervaly se udávají v milisekundách. Jedna sekunda je tedy tisíc milisekund. Rozdíl mezi těmito dvěma funkcemi je vcelku zásadní. Zatímco funkce setInterval() bude funkci volat každých x milisekund, funkce setTimeout() ji zavolá pouze jednou, a to za x milisekund. Proto funkci setTimeout() použijeme, chceme-li nastavit odklad nějaké akci a funkci setInterval() využijeme, když potřebujeme nastavit pravidelné opakování určité akce.

Postupné vypsání textu

Ukažme si použití časovače nejprve na jednoduchém příkladu. Budeme v něm postupně vypisovat text Ahoj světe. Nejprve bude vidět písmeno A, o sekundu později přiskočí h a takto se budou přidávat další písmena. Jakmile bude vypsán celý text, smaže se a začne to celé znovu.

V těle HTML nebudeme mít žádný element, vše vytvoříme až z JavaScriptu:

<!DOCTYPE html>
<html lang="cs-cz">
    <head>
        <meta charset="UTF-8" />
        <title>Animace textu</title>
        <script src="animace_textu.js"></script>
    </head>
    <body>
    </body>
</html>

V souboru animace_textu.js vytvoříme proměnnou text, kam uložíme náš výpis Ahoj světe. Poté vytvoříme element <p>, počkáme na načtení stránky a přidáme jej do těla dokumentu:

let text = "Ahoj světe";
let paragraph = document.createElement("p");

window.onload = function() {
    document.body.appendChild(paragraph);
}

Funkce zmenText()

Nyní vytvoříme funkci, která bude měnit text v našem elementu. Přidáme do ní podmínku, která ověří, zda již nemáme vypsaný celý text. Pokud ano, celý obsah odstavce vymažeme a budeme pokračovat od začátku.:

function zmenText() {

    if (paragraph.textContent == text) {
        paragraph.textContent = "";
    } else {
        // Sem doplníme další kód
    }
}

Uvedená podmínka je pro správnou funkčnost ukázky nezbytná. Pokud bychom zkusili na konci textu získat další písmeno, dostali bychom chybu, protože takové písmeno v naší proměnné text neexistuje.

Ve větvi else doplníme další kód pro zajištění postupného výpisu textu. Vezmeme písmeno (znak) z proměnné text, které přidáme ke stávajícímu textu elementu paragraph. Již víme, že vlastnost length obsahuje délku řetězce. Další znak k vypsání získáme tak, že změříme délku řetězce, který je vypsaný na obrazovku a použijeme jej jako index znaku proměnné text. Pomocí operátoru += jej pak vložíme do elementu paragraph.

Délka řetězce se počítá od 1, pro text s jedním znakem nám vrátí vlastnost length hodnotu 1. Znak na určité pozici textu získáváme pomocí indexu, který se počítá od nuly. První znak má tedy index 0 a tak dále. Proto znak na indexu aktuální délky textu vypsaného na obrazovce reprezentuje následující znak k vypsání.

Nakonec pomocí další podmínky ověříme, zda nevypisujeme mezeru. Pokud ano, zavoláme funkci zmenText() a vypíšeme ihned další znak, aby uživatel neměl pocit, že se aplikace zasekla:

let pismenoKpridani = text[paragraph.textContent.length];
paragraph.textContent += pismenoKpridani;

if (pismenoKpridani == " ") {
    zmenText();
}

Nastavení časovače

Nyní zbývá pouze spustit interval. Do obsluhy události onload přidáme funkci setInterval(), které jako první parametr předáme funkci zmenText() (bez závorek) a jako druhý parametr dosadíme číslo 1000, čímž nastavíme interval změny textu na jednu sekundu. Interval se spustí až po jedné sekundě. Do té doby to bude vypadat, že aplikace vůbec nereaguje, proto předtím ještě sami zavoláme funkci zmenText():

zmenText();
setInterval(zmenText, 1000);

Celý kód vypadá takto:

let text = "Ahoj světe";
let paragraph = document.createElement("p");

window.onload = function() {
    document.body.appendChild(paragraph);
    zmenText();
    setInterval(zmenText, 1000);
}

function zmenText() {
    if (paragraph.textContent == text) {
        paragraph.textContent = "";
    } else {
        let pismenoKpridani = text[paragraph.textContent.length];
        paragraph.textContent += pismenoKpridani;

        if (pismenoKpridani == " ") {
            zmenText();
        }
    }
}

Výsledek v prohlížeči:

Vypisující se text
localhost

Složitější animace

Bylo by hezké mít na webu i nějakou složitější animaci. Jednoduché animace lze vyřešit v CSS, ale u těch složitějších již musíme použít JavaScript. Celá pointa animací spočívá v tom, ž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ů. Každý obrázek bude mít atribut data-podzim. Cíleně budeme vybírat pouze tyto obrázky, protože na webu mohou být (a bývají) i jiné obrázky a ty nechceme ovlivňovat.

Stáhneme si obrázek listu níže a vložíme jej do nového projektu:

List - Základní konstrukce jazyka JavaScript

V HTML souboru doplníme opět pouze hlavičku a tělo necháme prázdné:

<!DOCTYPE html>
<html lang="cs-cz">
    <head>
        <meta charset="UTF-8" />
        <title>Podzimní výzdoba</title>
        <script src="listy.js"></script>
        <link href="styl.css" rel="stylesheet" />
    </head>
    <body>
    </body>
</html>

Do projektu si přidáme CSS soubor, ve kterém nastavíme obrázkům absolutní pozici a <body> nastylujme tak, aby nezobrazovalo scrollbary:

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

body {
    overflow:hidden;
}

V souboru listy.js v události onload nejprve vytvoříme elementy listů s data atributem data-podzim a vložíme je do těla HTML:

document.addEventListener("DOMContentLoaded", function() {
    for (let i = 0; i < 5; i++) {
        let img = document.createElement("img");
        img.src = "list.jpg";
        img.setAttribute("data-podzim", "");
        img.alt = "List";
        document.body.appendChild(img);
    }

    // Zde vzápětí nastavíme počáteční pozici listů

});

Pro nastavení události onload jsme tentokrát použili nám již známou metodu addEventListener().

Zjištění velikosti okna

Listům nyní budeme chtít nastavit výchozí pozici, kterou vypočítáme zleva jako pětinu šířky okna pro každý list. Horní pozici vypočítáme jako výšku okna mínus výška listu, aby při načtení stránky začaly sjíždět z horní hrany. Protože potřebujeme pracovat s velikostí okna, ukážeme si několik vlastností, které s tím souvisejí.

Velikost obrazovky

Velikost obrazovky zjistíme pomocí vlastnosti width a height na objektu screen. Chceme-li zjistit velikost obrazovky bez systémových panelů (např. taskbaru), použijeme vlastnosti availWidth a availHeight na objektu screen.

Velikost okna webové stránky

My však potřebujeme zjistit velikost plochy, kterou může zabírat naše aplikace. Vlastnosti okna webové stránky najdeme na objektu window. Šířku reprezentuje vlastnost innerWidth, výšku reprezentuje vlastnost innerHeight.

Nastavení CSS vlastností z JavaScriptu

Ještě nám chybí jedna podstatná informace o tom, jak se nastavují CSS vlastnosti elementům z JavaScriptu. Všechny DOM elementy mají vlastnost style, která obsahuje vlastnosti pojmenované jako CSS vlastnosti. Ty se nezapisují pomlčkovou, ale camelCase notací.

Barvu pozadí elementu <body> na červenou tedy nastavíme takto:

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

Nastavení počáteční pozice listů

Vraťme se k našemu padajícímu listí a nastavme mu počáteční pozice. Nezapomeneme na doplnění jednotky px. Namísto komentáře v kódu výše doplníme:

let listy = document.querySelectorAll("body > img[data-podzim]");
let index = 0;

for (let img of listy) {
    img.style.left = index * window.innerWidth / listy.length + "px";
    img.style.top = -img.height + "px";
    index++;
}

// Sem později doplníme funkci setInterval()

V tomto kódu nejprve získáme jednotlivé listy a vytvoříme proměnnou index s hodnotou 0. V cyklu poté nastavíme postupně každému listu pomocí CSS stejnou horní pozici style.top a levý okraj obrázku style.left posouváme násobkem proměnné index, kterou postupně inkrementujeme.

Funkce posun()

Nyní vytvoříme funkci posun(), která bude posouvat všechny listy dolů. Funkce opět 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í jednotky z CSS vlastnosti naparsovat. Následně přičteme nějakou rozumnou hodnotu, aby animace nebyla ani moc rychlá ani moc pomalá:

function posun(listy) {
    for (let img of listy) {
        let novaPozice = parseInt(img.style.top) + 2;
        if (novaPozice > window.innerHeight) {
            novaPozice = -img.height;
        }
        img.style.top = novaPozice + "px";
    }
}

Nezapomněli jsme ošetřit případ, kdy list vyjede z okna. V podmínce tedy nastavíme novou pozici na mínus výšku obrázku.

Nyní zbývá pouze doplnit v obsluze události načtení okna nastavení časovače, přidáme tedy poslední řádek:

setInterval(() => posun(listy), 20);

Aplikaci spustíme. Uvidíme, že listí bude padat shora dolů a po vytečení z obrazovky zase znovu:

Padající listí
localhost

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 naprogramujeme kolo štěstí. Pro jednoduchost si jej načteme ze statického obrázku. Vytvořme si tedy stránku s obrázkem a plátnem, které si poté v JavaScriptu načteme.

Stáhneme si obrázek níže a vložíme jej do nového projektu:

Kolo štěstí - Základní konstrukce jazyka JavaScript

Do těla HTML souboru pak vložíme obrázek a plátno:

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

Ve skriptu si objekty z HTML načtěme. K proměnným přidáme ještě proměnnou, kde budeme mít uložený úhel otočení nastavený na hodnotu 0. Obrázek nezapomeneme ze stránky skrýt:

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

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

V uvedeném kódu jsme původní obrázek pro ilustraci skryli pomocí CSS vlastnosti style.display. Stejného efektu bychom dosáhli také zavoláním metody removeChild(obrazek) na rodiči, tedy na document.body.

Funkce prekresli()

Dále vytvoříme funkci prekresli(), ve které nejprve vymažeme plátno. Pak přesuneme a otočíme kontext a znovu na něj kolo vykreslíme. Nakonec navýšíme hodnotu proměnné otoceni o jeden stupeň. Víme, že musíme uvést hodnotu v radiánech, kdy jeden stupeň odpovídá (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;
}

Ve funkci prekresli() jsme posunuli plátno na jeho aktuální střed ([250, 250]). Metodou rotate() pak otáčíme plátno kolem této pozice. Obrázek má výšku i šířku 450 pixelů, abychom jej nakreslili na původní místo, zadáme jako souřadnice zápornou hodnotu poloviny jeho velikosti a šířky.

Spuštění a zastavení animace

Nyní se vrátíme do těla metody addEventListener() a opět zde nejprve zavoláme funkci prekresli(), abychom nečekali na uplynutí prvního intervalu. Poté nastavíme opakované volání vykreslovací funkce s krátkým intervalem. Na konec metody doplníme tedy následující dva řádky:

prekresli();
let interval = setInterval(prekresli, 20);

Na rozdíl od předchozího příkladu zde navíc ukládáme volání funkce setInterval() do proměnné interval. Díky tomu budeme moci spuštěnou animaci ukončit. Když totiž uživatel opustí naši záložku prohlížeče, animace stále běží na pozadí a vytěžuje procesor.

Zastavení animace zajistíme funkcí clearInterval(), která bere jako parametr proměnnou obsahující spuštěnou animaci. Protože chceme animaci ukončit při opuštění stránky, zavoláme tuto funkci v obsluze události beforeunload. Celý kód umístíme také do těla metody addEventListener():

window.addEventListener("beforeunload", function() {
    clearInterval(interval);
});

Výsledek:

Kolo štěstí
localhost

Nyní bychom měli již zvládat základy práce s animacemi včetně jejich ukončení. Dokázat si to můžeme na cvičení. Jak zajistit, aby za nás webový prohlížeč spouštěl animaci, jen když je potřeba, si řekneme jindy :)

V následujícím cvičení, Řešené úlohy k 26. lekci JavaScriptu, si procvičíme nabyté zkušenosti z předchozích lekcí.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

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

 

Předchozí článek
Řešené úlohy k 23.-25. lekci JavaScriptu
Všechny články v sekci
Základní konstrukce jazyka JavaScript
Přeskočit článek
(nedoporučujeme)
Řešené úlohy k 26. lekci JavaScriptu
Článek pro vás napsal Michal Žůrek - misaz
Avatar
Uživatelské hodnocení:
595 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.
Aktivity