Animace v JavaScriptu mohou zvládnout vÄci, které CSS nedokáže.
NapÅÃklad pohyb po složité cestÄ s Äasovacà funkcÃ, která je jiná než Bézierova kÅivka, nebo animaci na plátnÄ.
Použità setInterval
Animaci je možné implementovat jako posloupnost snÃmků â obvykle malých zmÄn HTML/CSS vlastnostÃ.
NapÅÃklad pÅi mÄnÄnà style.left z 0px na 100px se bude posunovat element. A pokud jej zvÄtÅ¡Ãme v setInterval a budeme jej zvyÅ¡ovat o 2px s malými prodlevami, tÅeba 50krát za sekundu, pak to bude vypadat plynule. Je to stejný princip jako v kinÄ: 24 snÃmků za sekundu staÄÃ, aby film vypadal plynule.
Pseudokód může vypadat následovnÄ:
let ÄasovaÄ = setInterval(function() {
if (animace je kompletnÃ) clearInterval(ÄasovaÄ);
else zvyš style.left o 2px
}, 20); // zmÄna o 2px každých 20 ms, pÅibližnÄ 50 snÃmků za sekundu
ÃplnÄjšà pÅÃklad animace:
let zaÄátek = Date.now(); // pamatujeme si Äas zaÄátku
let ÄasovaÄ = setInterval(function() {
// kolik Äasu uplynulo od zaÄátku?
let uplynulýÄas = Date.now() - zaÄátek;
if (uplynulýÄas >= 2000) {
clearInterval(ÄasovaÄ); // po 2 sekundách ukonÄÃme animaci
return;
}
// vykreslÃme animaci v okamžiku uplynulýÄas
vykresli(uplynulýÄas);
}, 20);
// když uplynulýÄas postupuje od 0 do 2000
// left nabývá hodnot od 0px do 400px
function vykresli(uplynulýÄas) {
vláÄek.style.left = uplynulýÄas / 5 + 'px';
}
Pro ukázku kliknÄte sem:
<!DOCTYPE HTML>
<html>
<head>
<style>
#train {
position: relative;
cursor: pointer;
}
</style>
</head>
<body>
<img id="train" src="https://js.cx/clipart/train.gif">
<script>
train.onclick = function() {
let start = Date.now();
let timer = setInterval(function() {
let timePassed = Date.now() - start;
train.style.left = timePassed / 5 + 'px';
if (timePassed > 2000) clearInterval(timer);
}, 20);
}
</script>
</body>
</html>Použità requestAnimationFrame
PÅedstavme si, že máme nÄkolik animacÃ, které probÃhajà souÄasnÄ.
Pokud spustÃme každou zvlášť, pak i když každá bude mÃt setInterval(..., 20), prohlÞeÄ bude muset pÅekreslovat mnohem ÄastÄji než každých 20 ms.
Je to tÃm, že každá má jiný poÄáteÄnà Äas, takže âkaždých 20 msâ se mezi různými animacemi liÅ¡Ã. Intervaly nejsou sjednocené. Budeme tedy mÃt nÄkolik nezávislých bÄhů každých 20 ms.
Jinými slovy, toto:
setInterval(function() {
animace1();
animace2();
animace3();
}, 20)
â¦je lehÄà než tÅi nezávislá volánÃ:
setInterval(animace1, 20); // nezávislé animace
setInterval(animace2, 20); // na různých mÃstech skriptu
setInterval(animace3, 20);
TÄchto nÄkolik nezávislých pÅekreslenà by mÄlo být seskupeno dohromady, abychom prohlÞeÄi ulehÄili pÅekreslovánÃ. TÃm budeme ménÄ zatÄžovat CPU a animace bude plynulejÅ¡Ã.
JeÅ¡tÄ jednu vÄc bychom mÄli mÃt na pamÄti. NÄkdy je CPU pÅetÞená nebo existujà jiné důvody, proÄ pÅekreslovat ménÄ Äasto (napÅÃklad když je záložka prohlÞeÄe skrytá), takže ve skuteÄnosti bychom nemÄli spouÅ¡tÄt animaci každých 20 ms.
Jak se o tom vÅ¡ak dozvÃme v JavaScriptu? Existuje specifikace Äasovánà animacÃ, která poskytuje funkci requestAnimationFrame. Ta Åešà vÅ¡echny tyto i dalšà problémy.
Syntaxe:
let idPožadavku = requestAnimationFrame(callback)
TÃm se naplánuje spuÅ¡tÄnà funkce callback v nejbližšÃm okamžiku, kdy prohlÞeÄ bude chtÃt provádÄt animaci.
Pokud ve funkci callback provedeme zmÄny v elementech, budou seskupeny dohromady s jinými callbacky requestAnimationFrame a s CSS animacemi. MÃsto mnoha pÅepoÄtů geometrie a pÅekreslovánà se tedy provede pouze jeden.
Návratovou hodnotu idPožadavku můžeme použÃt ke zruÅ¡enà volánÃ:
// zruÅ¡Ãme naplánované provedenà callbacku
cancelAnimationFrame(idPožadavku);
Funkce callback má jeden argument â Äas v milisekundách uplynulý od zaÄátku naÄÃtánà stránky. Tento Äas můžeme zÃskat i volánÃm performance.now().
Obvykle se callback spouÅ¡tà velmi brzy, pokud nenà CPU pÅetÞena, baterie laptopu nenà témÄÅ vybitá nebo nenà jiný důvod k jeho nespuÅ¡tÄnÃ.
NásledujÃcà kód zobrazuje Äas mezi prvnÃmi 10 spuÅ¡tÄnÃmi requestAnimationFrame. Obvykle je to 10-20 ms:
<script>
let pÅedchozà = performance.now();
let kolikrát = 0;
requestAnimationFrame(function mÄÅ(Äas) {
document.body.insertAdjacentHTML("beforeEnd", Math.floor(Äas - pÅedchozÃ) + " ");
pÅedchozà = Äas;
if (kolikrát++ < 10) requestAnimationFrame(mÄÅ);
})
</script>
Strukturovaná animace
Nynà můžeme vytvoÅit univerzálnÄjšà animaÄnà funkci, založenou na requestAnimationFrame:
function animace({ÄasovánÃ, vykreslenÃ, trvánÃ}) {
let zaÄátek = performance.now();
requestAnimationFrame(function animace(Äas) {
// pomÄrÄasu jde od 0 do 1
let pomÄrÄasu = (Äas - zaÄátek) / trvánÃ;
if (pomÄrÄasu > 1) pomÄrÄasu = 1;
// vypoÄÃtáme aktuálnà stav animace
let postup = ÄasovánÃ(pomÄrÄasu)
vykreslenÃ(postup); // vykreslÃme ho
if (pomÄrÄasu < 1) {
requestAnimationFrame(animace);
}
});
}
Funkce animace pÅijÃmá 3 parametry, které v zásadÄ popisujà celou animaci:
trvánÃ-
Celkový Äas animace, napÅÃklad
1000. ÄasovánÃ(pomÄrÄasu)-
Äasovacà funkce, podobná CSS vlastnosti
transition-timing-function, která obdržà pomÄr uplynulého Äasu (0na zaÄátku,1na konci) a vrátà mÃru dokonÄenà animace (podobnÄ jakoyna BézierovÄ kÅivce).NapÅÃklad lineárnà funkce znamená, že animace pokraÄuje rovnomÄrnÄ stále stejnou rychlostÃ:
function lineárnÃ(pomÄrÄasu) { return pomÄrÄasu; }Jejà graf:
Je to jako
transition-timing-function: linear. Dále uvedeme zajÃmavÄjšà varianty. vykreslenÃ(postup)-
Funkce, která obdržà stav dokonÄenà animace a vykreslà jej. Hodnota
postup=0popisuje poÄáteÄnà stav animace,postup=1koncový stav.Tato funkce zajistà skuteÄné vykreslenà animace.
Může pÅesunovat element:
function vykresli(postup) { vláÄek.style.left = postup + 'px'; }â¦Nebo provádÄt cokoli jiného, můžeme animovat cokoli jakýmkoli způsobem.
Animujme pomocà našà funkce Å¡ÃÅku elementu width od 0 do 100%.
Pro ukázku kliknÄte na element:
function animate({duration, draw, timing}) {
let start = performance.now();
requestAnimationFrame(function animate(time) {
let timeFraction = (time - start) / duration;
if (timeFraction > 1) timeFraction = 1;
let progress = timing(timeFraction)
draw(progress);
if (timeFraction < 1) {
requestAnimationFrame(animate);
}
});
}<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
progress {
width: 5%;
}
</style>
<script src="animate.js"></script>
</head>
<body>
<progress id="elem"></progress>
<script>
elem.onclick = function() {
animate({
duration: 1000,
timing: function(timeFraction) {
return timeFraction;
},
draw: function(progress) {
elem.style.width = progress * 100 + '%';
}
});
};
</script>
</body>
</html>Kód animace:
animace({
trvánÃ: 1000,
ÄasovánÃ(pomÄrÄasu) {
return pomÄrÄasu;
},
vykreslenÃ(postup) {
elem.style.width = postup * 100 + '%';
}
});
Na rozdÃl od CSS animacà zde můžeme vytvoÅit jakoukoli Äasovacà a jakoukoli vykreslovacà funkci. Äasovacà funkce se neomezuje na Bézierovy kÅivky. A vykreslenà může zacházet za vlastnosti, vytvoÅit nové elementy pro animaci ve stylu ohÅostroje nebo cokoli jiného.
Äasovacà funkce
Výše jsme vidÄli nejjednoduššÃ, lineárnà Äasovacà funkci.
PodÃvejme se na jiné. VyzkouÅ¡Ãme animace pohybu s různými ÄasovacÃmi funkcemi, abychom vidÄli, jak fungujÃ.
UmocnÄnà na n-tou
Jestliže chceme animaci urychlit, můžeme použÃt postup umocnÄný na n-tou.
NapÅÃklad parabolickou kÅivku:
function naDruhou(pomÄrÄasu) {
return Math.pow(pomÄrÄasu, 2)
}
Graf:
ProhlédnÄte si ji v akci (kliknutÃm ji aktivujete):
â¦Nebo kubickou kÅivku nebo jeÅ¡tÄ vÄtšà n. Zvýšenà exponentu způsobà vÄtšà urychlenÃ.
Zde je graf pro postup na 5-tou:
V akci:
Oblouk
Funkce:
function kruh(pomÄrÄasu) {
return 1 - Math.sin(Math.acos(pomÄrÄasu));
}
Graf:
ZpÄt: výstÅel z luku
NásledujÃcà funkce provádà âvýstÅel z lukuâ. Nejprve ânatáhneme tÄtivuâ a pak âvystÅelÃmeâ.
Na rozdÃl od pÅedchozÃch funkcà závisà na dalÅ¡Ãm parametru x, âkoeficientu pružnostiâ. Je jÃm definována délka ânataženà tÄtivyâ.
Kód:
function zpÄt(x, pomÄrÄasu) {
return Math.pow(pomÄrÄasu, 2) * ((x + 1) * pomÄrÄasu - x)
}
Graf pro x = 1.5:
Pro animaci ji použijeme se specifickou hodnotou x. PÅÃklad pro x = 1.5:
SkákánÃ
PÅedstavme si, že upustÃme mÃÄ. Dopadne na zem, nÄkolikrát odskoÄà a pak se zastavÃ.
Funkce skákánà provádà totéž, ale v obráceném poÅadÃ: âskákánÃâ zaÄne okamžitÄ. PoužÃvá k tomu nÄkolik speciálnÃch koeficientů:
function skákánÃ(pomÄrÄasu) {
for (let a = 0, b = 1; 1; a += b, b /= 2) {
if (pomÄrÄasu >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * pomÄrÄasu) / 4, 2) + Math.pow(b, 2)
}
}
}
V akci:
Elastická animace
Dalšà âelastickáâ funkce, která pÅijÃmá dalšà parametr x jako âúvodnà rozsahâ:
function elastická(x, pomÄrÄasu) {
return Math.pow(2, 10 * (pomÄrÄasu - 1)) * Math.cos(20 * Math.PI * x / 3 * pomÄrÄasu)
}
Graf pro x=1.5:
V akci pro x=1.5:
Opak: ease*
Máme tedy sadu ÄasovacÃch funkcÃ. Jejich pÅÃmá aplikace se nazývá âeaseInâ.
NÄkdy potÅebujeme zobrazit animaci v obráceném poÅadÃ. To provedeme pomocà transformace âeaseOutâ.
easeOut
V režimu âeaseOutâ je funkce Äasovánà umÃstÄna do obalu ÄasovánÃEaseOut:
ÄasovánÃEaseOut(pomÄrÄasu) = 1 - ÄasovánÃ(1 - pomÄrÄasu)
Jinými slovy, máme âtransformaÄnÃâ funkci vytvoÅEaseOut, která vezme âobyÄejnouâ Äasovacà funkci a vrátà obal kolem nÃ:
// pÅijÃmá Äasovacà funkci, vracà transformovanou variantu
function vytvoÅEaseOut(ÄasovánÃ) {
return function(pomÄrÄasu) {
return 1 - ÄasovánÃ(1 - pomÄrÄasu);
}
}
Můžeme napÅÃklad vzÃt výše uvedenou funkci skákánà a aplikovat ji:
let skákánÃEaseOut = vytvoÅEaseOut(skákánÃ);
Pak skákánà nebude na zaÄátku, ale na konci animace. Vypadá to jeÅ¡tÄ lépe:
#brick {
width: 40px;
height: 20px;
background: #EE6B47;
position: relative;
cursor: pointer;
}
#path {
outline: 1px solid #E8C48E;
width: 540px;
height: 20px;
}<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
<script src="https://js.cx/libs/animate.js"></script>
</head>
<body>
<div id="path">
<div id="brick"></div>
</div>
<script>
function makeEaseOut(timing) {
return function(timeFraction) {
return 1 - timing(1 - timeFraction);
}
}
function bounce(timeFraction) {
for (let a = 0, b = 1; 1; a += b, b /= 2) {
if (timeFraction >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
}
}
}
let bounceEaseOut = makeEaseOut(bounce);
brick.onclick = function() {
animate({
duration: 3000,
timing: bounceEaseOut,
draw: function(progress) {
brick.style.left = progress * 500 + 'px';
}
});
};
</script>
</body>
</html>Zde vidÃme, jak transformace mÄnà chovánà funkce:
Pokud je nÄjaký efekt, napÅÃklad skákánÃ, na zaÄátku animace, zobrazà se na konci.
V uvedeném grafu má obvyklé skákánà Äervenou barvu a skákánà easeOut modrou.
- Obvyklé skákánà â objekt skáÄe na spodku, pak nakonec ostÅe vyskoÄà na vrch.
- Po
easeOutâ nejprve skoÄà na vrch, pak skáÄe tam.
easeInOut
Můžeme také zobrazit efekt jak na zaÄátku, tak na konci animace. Tato transformace se nazývá âeaseInOutâ.
Ze zadané Äasovacà funkce vypoÄÃtáme stav animace následovnÄ:
if (pomÄrÄasu <= 0.5) { // prvnà polovina animace
return ÄasovánÃ(2 * pomÄrÄasu) / 2;
} else { // druhá polovina animace
return (2 - ÄasovánÃ(2 * (1 - pomÄrÄasu))) / 2;
}
Kód obalu:
function vytvoÅEaseInOut(ÄasovánÃ) {
return function(pomÄrÄasu) {
if (pomÄrÄasu < .5)
return ÄasovánÃ(2 * pomÄrÄasu) / 2;
else
return (2 - ÄasovánÃ(2 * (1 - pomÄrÄasu))) / 2;
}
}
skákánÃEaseInOut = vytvoÅEaseInOut(skákánÃ);
V akci, skákánÃEaseInOut:
#brick {
width: 40px;
height: 20px;
background: #EE6B47;
position: relative;
cursor: pointer;
}
#path {
outline: 1px solid #E8C48E;
width: 540px;
height: 20px;
}<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
<script src="https://js.cx/libs/animate.js"></script>
</head>
<body>
<div id="path">
<div id="brick"></div>
</div>
<script>
function makeEaseInOut(timing) {
return function(timeFraction) {
if (timeFraction < .5)
return timing(2 * timeFraction) / 2;
else
return (2 - timing(2 * (1 - timeFraction))) / 2;
}
}
function bounce(timeFraction) {
for (let a = 0, b = 1; 1; a += b, b /= 2) {
if (timeFraction >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
}
}
}
let bounceEaseInOut = makeEaseInOut(bounce);
brick.onclick = function() {
animate({
duration: 3000,
timing: bounceEaseInOut,
draw: function(progress) {
brick.style.left = progress * 500 + 'px';
}
});
};
</script>
</body>
</html>Transformace âeaseInOutâ spojuje dva grafy do jednoho: easeIn (obvyklé) pro prvnà polovinu animace a easeOut (obrácené) pro druhou polovinu.
Efekt jasnÄ uvidÃme, když si porovnáme grafy easeIn, easeOut a easeInOut Äasovacà funkce kruh:
- Äervená je obvyklá varianta funkce
kruh(easeIn). - Zelená â
easeOut. - Modrá â
easeInOut.
Jak vidÃme, graf prvnà poloviny animace je zmenÅ¡ený easeIn a druhé poloviny zmenÅ¡ený easeOut. Výsledkem je, že animace zaÄne a skonÄà stejným efektem.
ZajÃmavÄjšà vykreslovánÃ
MÃsto pÅesunutà elementu můžeme udÄlat nÄco jiného. StaÄà napsat vhodnou funkci vykreslenÃ.
Zde je animované âskákajÃcÃâ psanà textu:
textarea {
display: block;
border: 1px solid #BBB;
color: #444;
font-size: 110%;
}
button {
margin-top: 10px;
}<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
<script src="https://js.cx/libs/animate.js"></script>
</head>
<body>
<textarea id="pÅÃkladTextu" rows="5" cols="60">He took his vorpal sword in hand:
Long time the manxome foe he soughtâ
So rested he by the Tumtum tree,
And stood awhile in thought.
</textarea>
<button onclick="animaceTextu(pÅÃkladTextu)">Spustit animované psanÃ!</button>
<script>
function animaceTextu(textArea) {
let text = textArea.value;
let kam = text.length,
odkud = 0;
animate({
duration: 5000,
timing: skákánÃ,
draw: function(postup) {
let výsledek = (kam - odkud) * postup + odkud;
textArea.value = text.slice(0, Math.ceil(výsledek))
}
});
}
function skákánÃ(pomÄrÄasu) {
for (let a = 0, b = 1; 1; a += b, b /= 2) {
if (pomÄrÄasu >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * pomÄrÄasu) / 4, 2) + Math.pow(b, 2)
}
}
}
</script>
</body>
</html>ShrnutÃ
JavaScript může pomoci s animacemi, které CSS nedokáže správnÄ zvládnout nebo které potÅebujà hlubšà kontrolu. Animace v JavaScriptu by mÄly být implementovány funkcà requestAnimationFrame. Tato zabudovaná metoda umožÅuje nastavit callbackovou funkci, která se spustÃ, až bude prohlÞeÄ pÅipravovat pÅekreslenÃ. Obvykle k tomu docházà velmi brzy, ale pÅesný Äas závisà na prohlÞeÄi.
Když je stránka na pozadÃ, nedocházà k žádným pÅekreslenÃm, takže callback se nespustÃ: animace bude pozastavena a nebude spotÅebovávat zdroje. To je výborné.
NásledujÃcà pomocná funkce animace sloužà k nastavenà vÄtÅ¡iny animacÃ:
function animace({ÄasovánÃ, vykreslenÃ, trvánÃ}) {
let zaÄátek = performance.now();
requestAnimationFrame(function animace(Äas) {
// pomÄrÄasu jde od 0 do 1
let pomÄrÄasu = (Äas - zaÄátek) / trvánÃ;
if (pomÄrÄasu > 1) pomÄrÄasu = 1;
// vypoÄÃtáme aktuálnà stav animace
let postup = ÄasovánÃ(pomÄrÄasu);
vykreslenÃ(postup); // vykreslÃme ho
if (pomÄrÄasu < 1) {
requestAnimationFrame(animace);
}
});
}
Jejà volby:
trvánÃâ celkový Äas animace v milisekundách.ÄasovánÃâ funkce poÄÃtajÃcà postup animace. Obdržà pomÄr Äasu od 0 do 1 a vrátà postup animace, obvykle také od 0 do 1.vykreslenÃâ funkce vykreslujÃcà animaci.
SamozÅejmÄ bychom ji mohli vylepÅ¡it a pÅidat dalšà vychytávky, ale animace v JavaScriptu se nepoužÃvajà každý den. PoužÃvajà se k provedenà nÄÄeho zajÃmavého a nestandardnÃho. Dalšà vlastnosti tedy budete chtÃt pÅidat až ve chvÃli, kdy je budete potÅebovat.
Animace v JavaScriptu mohou využÃvat jakoukoli Äasovacà funkci. Uvedli jsme mnoho pÅÃkladů a transformacÃ, abychom je uÄinili jeÅ¡tÄ vÅ¡estrannÄjÅ¡Ãmi. Na rozdÃl od CSS se zde nemusÃme omezovat na Bézierovy kÅivky.
Totéž platà pro vykreslenÃ: můžeme animovat cokoli, nejenom CSS vlastnosti.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)