28. diel - Časovače a animácie v JavaScripte
V predchádzajúcom cvičení, Riešené úlohy k 24.-27. lekciu JavaScriptu, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V tomto tutoriále základov JavaScriptu si ukážeme, ako našim kresbám nastaviť časovače a docieliť tým efekt animácie na webovej stránke.
Časovače a animácie
Časovače môžeme v JavaScripte obsluhovať pomocou dvoch funkcií, ktoré si teraz predstavíme.
Funkcia setInterval()
a
setTimeout()
Funkcia setInterval()
a setTimeout()
prijímajú
dva parametre. Volanú funkciu (bez zátvoriek) a
časový interval určujúci, ako často alebo za ako dlho sa
bude táto funkcia volať. Časové intervaly sa udávajú v milisekundách.
Jedna sekunda je teda tisíc milisekúnd. Rozdiel medzi týmito dvoma funkciami
je vcelku zásadný. Zatiaľ čo funkcia setInterval()
bude funkciu
volať každých x
milisekúnd, funkcia
setTimeout()
ju zavolá iba raz, a to za
x
milisekúnd. Preto funkciu setTimeout()
použijeme,
ak chceme nastaviť odklad nejakej akcii a funkciu
setInterval()
využijeme, keď potrebujeme nastaviť
pravidelné opakovanie určitej akcie.
Postupné vypísanie textu
Ukážme si použitie časovača najskôr na jednoduchom príklade. Budeme v
ňom postupne vypisovať text Hello World
. Najprv bude vidieť
písmeno H
, o sekundu neskôr priskočí e
a takto sa
budú pridávať ďalšie písmená. Akonáhle bude vypísaný celý text,
zmaže sa a začne to celé znova.
V tele HTML nebudeme mať žiadny element, všetko vytvoríme až z JavaScriptu:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Text animation</title> <script src="text_animation.js"></script> </head> <body> </body> </html>
V súbore text_animation.js
vytvoríme premennú
text
, kam uložíme náš výpis Hello World
. Potom
vytvoríme element <p>
, počkáme na načítanie stránky a
pridáme ho do tela dokumentu:
let text = "Hello World"; let paragraph = document.createElement("p"); window.onload = function() { document.body.appendChild(paragraph); }
Funkcia changeText()
Teraz vytvoríme funkciu, ktorá bude meniť text v našom elemente. Pridáme do nej podmienku, ktorá overí, či už nemáme vypísaný celý text. Ak áno, celý obsah odseku vymažeme a budeme pokračovať od začiatku:
function changeText() { if (paragraph.textContent == text) { paragraph.textContent = ""; } else { // We'll add some more code here } }
Uvedená podmienka je pre správnu funkčnosť ukážky
nevyhnutná. Ak by sme skúsili na konci textu získať ďalšie písmeno,
dostali by sme chybu, pretože takéto písmeno v našej premennej
text
neexistuje.
Vo vetve else
doplníme ďalší kód na zaistenie postupného
výpisu textu. Vezmeme písmeno (znak) z premennej text
, ktoré
pridáme k existujúcemu textu elementu paragraph
. Už vieme, že
vlastnosť length
obsahuje dĺžku reťazca. Ďalší znak na
vypísanie získame tak, že zmeriame dĺžku reťazca, ktorý je vypísaný na
obrazovku a použijeme ho ako index znaku premennej text
. Pomocou
operátora +=
ho potom vložíme do elementu
paragraph
.
Dĺžka reťazca sa počíta od 1
, pre text s
jedným znakom nám vráti vlastnosť length
hodnotu
1
. Znak na určitej pozícii textu získavame pomocou
indexu, ktorý sa počíta od nuly. Prvý znak
má teda index 0
a tak ďalej. Preto znak na indexe aktuálnej
dĺžky textu vypísaného na obrazovke reprezentuje nasledujúci znak na
vypísanie.
Nakoniec pomocou ďalšej podmienky overíme, či nevypisujeme medzeru. Ak
áno, zavoláme funkciu changeText()
a vypíšeme ihneď ďalší
znak, aby používateľ nemal pocit, že sa aplikácia zasekla:
let letterToType = text[paragraph.textContent.length]; paragraph.textContent += letterToType; if (letterToType == " ") { chageText(); }
Nastavenie časovača
Teraz zostáva iba spustiť interval. Do obsluhy udalosti onload
pridáme funkciu setInterval()
, ktoré ako prvý parameter
odovzdáme funkciu changeText()
(bez zátvoriek) a ako druhý
parameter dosadíme číslo 1000
, čím nastavíme interval zmeny
textu na jednu sekundu. Interval sa spustí až po jednej sekunde. Dovtedy to
bude vyzerať, že aplikácia vôbec nereaguje, preto predtým ešte sami
zavoláme funkciu changeText()
:
changeText(); setInterval(changeText, 1000);
Celý kód vyzerá takto:
let text = "Hello World"; let paragraph = document.createElement("p"); window.onload = function() { document.body.appendChild(paragraph); changeText(); setInterval(changeText, 1000); } function changeText() { if (paragraph.textContent == text) { paragraph.textContent = ""; } else { let letterToType = text[paragraph.textContent.length]; paragraph.textContent += letterToType; if (letterToType == " ") { changeText(); } } }
Výsledok v prehliadači:
Zložitejšie animácie
Bolo by pekné mať na webe aj nejakú zložitejšiu animáciu. Jednoduché animácie je možné vyriešiť v CSS, ale u tých zložitejších už musíme použiť JavaScript. Celá pointa animácií spočíva v tom, že v nejakom intervale ovplyvňujeme vlastnosti animovaného objektu.
Vezmeme si napríklad jesennú výzdobu webu. Naprogramujeme skript, ktorý
nechá padať lístie zhora nadol. Každý obrázok bude mať atribút
data-autumn
. Cielene budeme vyberať iba tieto obrázky, pretože
na webe môžu byť (a bývajú) aj iné obrázky a tie nechceme
ovplyvňovať.
Stiahneme si obrázok listu nižšie a vložíme ho do nového projektu:
V HTML súbore doplníme opäť iba hlavičku a telo necháme prázdne:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Autumn decoration</title> <script src="leaves.js"></script> <link href="style.css" rel="stylesheet" /> </head> <body> </body> </html>
Do projektu si pridáme CSS súbor, v ktorom nastavíme obrázkom absolútnu
pozíciu a <body>
naštylujeme tak, aby nezobrazovalo
scrollbary:
body > img[data-autumn] { position: absolute; } body { overflow: hidden; }
V súbore leaves.js
v udalosti onload
najprv
vytvoríme elementy listov s atribútom data-autumn
a vložíme ich
do tela HTML:
document.addEventListener("DOMContentLoaded", function() { for (let i = 0; i < 5; i++) { let img = document.createElement("img"); img.src = "leaf.jpg"; img.setAttribute("data-autumn", ""); img.alt = "Leaf"; document.body.appendChild(img); } // We will set the initial position of the leaves });
Na nastavenie udalosti onload
sme tentoraz použili nám už
známu metódu addEventListener()
.
Zistenie veľkosti okna
Listom teraz budeme chcieť nastaviť východiskovú pozíciu, ktorú vypočítame zľava ako pätinu šírky okna pre každý list. Hornú pozíciu vypočítame ako výšku okna mínus výška listu, aby pri načítaní stránky začali schádzať z hornej hrany. Pretože potrebujeme pracovať s veľkosťou okna, ukážeme si niekoľko vlastností, ktoré s tým súvisia.
Veľkosť obrazovky
Veľkosť obrazovky zistíme pomocou vlastnosti width
a
height
na objekte screen
. Ak chceme zistiť veľkosť
obrazovky bez systémových panelov (napr. taskbaru),
použijeme vlastnosti availWidth
a availHeight
na
objekte screen
.
Veľkosť okna webovej stránky
My však potrebujeme zistiť veľkosť plochy, ktorú môže zaberať naša
aplikácia. Vlastnosti okna webovej stránky nájdeme na objekte
window
. Šírku reprezentuje vlastnosť innerWidth
,
výšku reprezentuje vlastnosť innerHeight
.
Nastavenie CSS vlastností z JavaScriptu
Ešte nám chýba jedna podstatná informácia o tom, ako sa
nastavujú CSS vlastnosti elementom z JavaScriptu. Všetky
elementy DOM majú vlastnosť style
, ktorá obsahuje vlastnosti
pomenované ako CSS vlastnosti. Tie sa nezapisujú pomlčkovú, ale camelCase
notácií.
Farbu pozadia elementu <body>
na červenú teda nastavíme
takto:
document.body.style.backgroundColor = "red";
Nastavenie počiatočnej pozície listov
Vráťme sa k nášmu padajúcemu lístiu a nastavme mu počiatočné
pozície. Nezabudneme na doplnenie jednotky px
. Namiesto komentára
v kóde vyššie doplníme:
let leaves = document.querySelectorAll("body > img[data-autumn]"); let index = 0; for (let leaf of leaves) { leaf.style.left = index * window.innerWidth / leaves.length + "px"; leaf.style.top = -img.height + "px"; index++; } // We will add the setInterval() function here later
V tomto kóde najskôr získame jednotlivé listy a vytvoríme premennú
index
s hodnotou 0
. V cykle potom nastavíme postupne
každému listu pomocou CSS rovnakú hornú pozíciu style.top
a
ľavý okraj obrázku style.left
posúvame násobkom premennej
index
, ktorú postupne inkrementujeme.
Funkcia shift()
Teraz vytvoríme funkciu shift()
, ktorá bude posúvať všetky
listy dole. Funkcia opäť cyklom prejde všetky listy a nastaví im novú
pozíciu. Tú získa na základe súčasnej pozície listu, ktorú musíme
kvôli odstráneniu jednotky z CSS vlastnosti naparzovať. Následne
pripočítame nejakú rozumnú hodnotu, aby animácia nebola ani veľmi rýchla
ani veľmi pomalá:
function shift(leaves) { for (let leaf of leaves) { let newPosition = parseInt(leaf.style.top) + 2; if (newPosition > window.innerHeight) { newPosition = -leaf.height; } leaf.style.top = newPosition + "px"; } }
Nezabudli sme ošetriť prípad, keď list vyjde z okna. V podmienke teda nastavíme novú pozíciu na mínus výšku obrázku.
Teraz zostáva iba doplniť v obsluhe udalosti načítanie okna nastavenia časovača, pridáme teda posledný riadok:
setInterval(() => shift(leaves), 20);
Aplikáciu spustíme. Uvidíme, že lístie bude padať zhora nadol a po vytečení z obrazovky zase znova:
Animácie na plátne
V podobnom duchu sa nesú animácie na plátne, kde v určitom intervale celé plátno vymažeme a znovu vykreslíme, a tak stále dookola. Ako ukážku si naprogramujeme koleso šťastia. Pre jednoduchosť si ho načítame zo statického obrázku. Vytvorme si teda stránku s obrázkom a plátnom, ktoré si potom v JavaScripte načítame.
Stiahneme si obrázok nižšie a vložíme ho do nového projektu:
Do tela HTML súboru potom vložíme obrázok a plátno:
<img src="wheel.png" id="wheel" /> <canvas id="canvas" width="500" height="500"></canvas>
V skripte si objekty z HTML načítajme. K premenným pridáme ešte
premennú, kde budeme mať uložený uhol otočenia nastavený na hodnotu
0
. Obrázok nezabudneme zo stránky skryť:
let canvas; let context; let image; let rotation = 0; document.addEventListener("DOMContentLoaded", function() { canvas = document.getElementById("canvas"); context = canvas.getContext("2d"); image = document.getElementById("wheel"); image.style.display = "none"; });
V uvedenom kóde sme pôvodný obrázok pre ilustráciu skryli
pomocou CSS vlastnosti style.display
. Rovnaký efekt by sme
dosiahli aj zavolaním metódy removeChild(image)
na rodičovi,
teda na document.body
.
Funkcia redraw()
Ďalej vytvoríme funkciu redraw()
, v ktorej najskôr vymažeme
plátno. Potom presunieme a otočíme kontext a znova naň koleso vykreslíme.
Nakoniec navýšime hodnotu premennej rotation
o jeden stupeň.
Vieme, že musíme uviesť hodnotu v radiánoch, keď jeden stupeň zodpovedá
(2 * PI) / 360
:
function redraw() { context.clearRect(0, 0, 500, 500); context.save(); context.translate(250, 250); context.rotate(rotation); context.drawImage(image, -225, -225); context.restore(); rotation += (2 * Math.PI) / 360; }
Vo funkcii redraw()
sme posunuli plátno na jeho aktuálny stred
([250, 250]
). Metódou rotate()
potom otáčame
plátno okolo tejto pozície. Obrázok má výšku aj šírku 450 pixelov, aby
sme ho nakreslili na pôvodné miesto, zadáme ako súradnice zápornú hodnotu
polovice jeho veľkosti a šírky.
Spustenie a zastavenie animácie
Teraz sa vrátime do tela metódy addEventListener()
a opäť tu
najskôr zavoláme funkciu redraw()
, aby sme nečakali na uplynutie
prvého intervalu. Potom nastavíme opakované volanie vykresľovacej funkcie s
krátkym intervalom. Na koniec metódy doplníme teda nasledujúce dva
riadky:
redraw(); let interval = setInterval(redraw, 20);
Na rozdiel od predchádzajúceho príkladu tu navyše ukladáme volanie
funkcie setInterval()
do premennej interval
. Vďaka
tomu budeme môcť spustenú animáciu ukončiť. Keď totiž
užívateľ opustí našu záložku prehliadača, animácia stále beží na
pozadí a vyťažuje procesor.
Zastavenie animácie zaistíme funkciou clearInterval()
, ktorá
berie ako parameter premennú obsahujúcu spustenú animáciu. Pretože chceme
animáciu ukončiť pri opustení stránky, zavoláme túto funkciu v obsluhe
udalosti beforeunload
. Celý kód umiestnime aj do tela metódy
addEventListener()
:
window.addEventListener("beforeunload", function() { clearInterval(interval); });
Výsledok:
Teraz by sme mali už zvládať základy práce s animáciami vrátane ich ukončenia. Dokázať si to môžeme na cvičenie. Ako zaistiť, aby za nás webový prehliadač spúšťal animáciu, len keď je potreba, si povieme inokedy
V nasledujúcom cvičení, Riešené úlohy k 28. lekcii JavaScriptu, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 5x (217.48 kB)
Aplikácia je vrátane zdrojových kódov v jazyku JavaScript