You are on page 1of 19

JS ANIMACIJE

JS animacije mogu da rade stvari koje CSS ne može.


Na primer, kretanje kroz kompleksan put, sa vremenskom f-jom drugačijom od Bezjeove krive,
ili animacija na kanvasu.

Korišćenje setInterval
Animacija može biti implementirana kao sekvenca okvira – obično male promene HTML/CSS
svojstava.
Na primer, promena style.left sa 0px na 100px pomera element. I ukoliko povećamo setInterval,
pomeranjem za 2px sa malim odlaganjem, na primer 50 puta po sekundi, onda ide glatko. To je
isti princip kao i na filmu: 24 frejma po sekundi je dovoljno da izgleda glatko.
Pseudo-kod može da izgleda ovako:
let timer = setInterval(function() {
if (animation complete) clearInterval(timer);
else increase style.left by 2px
}, 20); // change by 2px every 20ms, about 50 frames per second

Kompleksniji primer animacije:


let start = Date.now(); // remember start time

let timer = setInterval(function() {


// how much time passed from the start?
let timePassed = Date.now() - start;

if (timePassed >= 2000) {


clearInterval(timer); // finish the animation after 2 seconds
return;
}

// draw the animation at the moment timePassed


draw(timePassed);

}, 20);

// as timePassed goes from 0 to 2000


// left gets values from 0px to 400px
function draw(timePassed) {
train.style.left = timePassed / 5 + 'px';
}

1
Rezultat izgleda ovako:

Html kod je:


<!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';

2
if (timePassed > 2000) clearInterval(timer);

}, 20);
}
</script>

</body>

</html>

requestAnimationFrame
Hajde da zamislimo da imamo nekoliko animacija koje idu istovremeno.
Ako ih pokrenemo odvojeno, iako svaka od njih ima setInterval(..., 20), pretraživač bi morao da
se 'prefarba' češće od 20ms.
To je zato što oni imaju različita polazna vremena tako da svakih '20ms' se razlikuje za svaku
animaciju. Njihovi intervali nisu poravnati. Tako da ćemo imati nekoliko nezavisnih pokretanja u
20ms.
Drugim rečima, ovo:
setInterval(function() {
animate1();
animate2();
animate3();
}, 20)
...je lakše neko tri nezavisna poziva:
setInterval(animate1, 20); // independent animations
setInterval(animate2, 20); // in different places of the script
setInterval(animate3, 20);
Ovih nekoliko nezavisnih 'ponovnih izvlačenja' treba zajedno grupisati, da bi izvlačenje iznova i
iznova bilo lakše za pretraživač i samim tim izgleda fluidnije.
Postoji još jedna stvar koju treba imati na umu. Ponekad kada je CPU preopterećen, ili postoji još
neki razlog koji dovodi do usporavanja (npr kad je tab pretraživača sakriven), ne bi trebali da ga
pokrećemo svakih 20ms.
Ali kako da to znamo u JS? Postoji specifikacija Animation timing koja omogućava f-ju
requestAnimationFrame. Ona adresira sve probleme čak i više.
Sintaksa je:
let requestId = requestAnimationFrame(callback)

3
To rezerviše callback f-ju da se pokreće u najbližem vremenu kada pretraživač hoće da pokrene
aplikaciju.
Ako uradimo izmene u elementu i callback- u one se grupišu zajedno sa drugim
requestAnimationFrame callback-ovima i sa CSS animacijama.
Povratna vrednost requestId se može koristiti za otkazivanje poziva:
// cancel the scheduled execution of callback
cancelAnimationFrame(requestId);

Callback ima jedan argument – vreme koje je proteklo od početka učitavanja stranice u
milisekundama. Ovo vreme se takođe može dobiti pozivom performance.now().
Obično se callback pokrene veoma brzo, sem ukoliko je CPU preopterećen ili je baterija laptopa
pri kraju, ili ima neki drugi razlog.
Kod ispod prikazuje vreme između prvih 10 pokretanja, za requestAnimationFrame. Obično je
10-20ms:
<script>
let prev = performance.now();
let times = 0;

requestAnimationFrame(function measure(time) {
document.body.insertAdjacentHTML("beforeEnd", Math.floor(time - prev) + " ");
prev = time;

if (times++ < 10) requestAnimationFrame(measure);


})
</script>

Strukturisana animacija
Sada možemo da pravimo univerzalniju f-ju animacije baziranu na requestAnimationFrame:
function animate({timing, draw, duration}) {

let start = performance.now();

requestAnimationFrame(function animate(time) {
// timeFraction goes from 0 to 1
let timeFraction = (time - start) / duration;
if (timeFraction > 1) timeFraction = 1;

// calculate the current animation state


let progress = timing(timeFraction)

draw(progress); // draw it

if (timeFraction < 1) {

4
requestAnimationFrame(animate);
}

});
}

Funkcija animate prihvata 3 parametra koja u suštini opisuju animaciju:


duration
Ukupno vreme animacije. 1000
timing(timeFraction)
Vremenska f-ja, kao CSS svojstvo transition-timing-function koje uzima frakciju vremena koje
je proteklo (0 na početku, 1 na kraju) i vraća završetak animacije (kao y kod Bezjeove krive).
Na primer, linearna f-ja znači da animacija ide ravnomernom brzinom:
function linear(timeFraction) {
return timeFraction;
}

To je baš kao transition-timing-function: linear. Postoji još interesantnih varijanti koje ćemo
dalje raditi.

5
draw(progress)
F-ja koja uzima stanje završetka animacije i crta ga. Vrednost progress=0 označava početak
stanja animacije a progress=1 – kraj stanja.
Ovo je ta f-ja koja zapravo izvlači animaciju.
Može da pomeri element:
function draw(progress) {
train.style.left = progress + 'px';
}

... ili da uradi bilo šta drugo. Možemo da animiramo bilo šta na bilo koji način.
Hajde da animiramo širinu elementa od 0 do 100% koristeći našu f-ju.
Evo primera:
html kod:
<!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>

6
</html>

js kod:
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);
}

});
}

Kod za to
animate({
duration: 1000,
timing(timeFraction) {
return timeFraction;
},
draw(progress) {
elem.style.width = progress * 100 + '%';
}
});

Za razliku od CSS animacija, možemo da napravimo bilo koju vremensku f-ju i bilo koju f-ju za
crtanje ovde. Vremenska f-ja nije ograničena Bezjeovom krivom.
I draw može da ide preko svojstava, da kreira nove elemente za animacije vatrometa itd.

7
Vremenske f-je
Videli smo najjednostavniju, linearnu vremensku f-ju u primeru iznad.
Hajde da vidimo još njih. Pokušaćemo pokretne animacije sa različitim tajming f-jama da vidimo
kako one rade.

n
Ako želimo da ubrzamo animaciju možemo da koristimo progress sa moćnim n.
Na primer, parabolična kriva:
function quad(timeFraction) {
return Math.pow(timeFraction, 2)
}

Evo grafičkog prikaza za progress u mići 5:

8
arc
F-ja:
function circ(timeFraction) {
return 1 - Math.sin(Math.acos(timeFraction));
}

Grafikon:

9
Back: bow shooting
Ova f-ja radi 'bow shooting'. Prvo povlačimo tetivu luka i onda 'pucamo'.
Za razliku od prethodnih f-ja, ova zavisi od dodatnog parametra x, tj. koeficijenta elastičnosti.
Udaljenost luka od povučene tetive je definisana ovim.
Kod je:
function back(x, timeFraction) {
return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x)
}

Grafik za x=1,5

10
Bounce
Zamislite da puštamo loptu. Ona pada dole, i odbija se od površinu nekoliko puta i onda staje.
Bounce f-ja radi isto, ali obrnutim redosledom: 'odbijanje' počinje odmah.
Koristi nekoliko specijalnih koeficijenata za to:
function bounce(timeFraction) {
for (let a = 0, b = 1, result; 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)
}
}
}

Elastične animacije
Još jedna 'elastična' f-ja koja prihvata dodatni parametar x, za inicijalni 'domet
function elastic(x, timeFraction) {
return Math.pow(2, 10 * (timeFraction - 1)) * Math .cos(20 * Math.PI * x / 3 * timeFraction)
}

11
12
ease*
Imamo kolekciju vremenskih f-ja. Njihova direktna aplikacija se zove 'easeIn'.
Ponekad je potrebno da prikažemo animaciju u obrnutom redosledu. To se radi pomoću 'easeOut'
transformacije.
easeOut
U easeOut modu timing f-ja je stavljena u omotač timingEaseOut:
timingEaseOut(timeFraction) = 1 - timing(1 - timeFraction)

Drugim rečima, imamo f-ju transformacije makeEaseOut koja uzima ‘regularnu’ vremensku f-ju
i vraća omotač oko nje:
// accepts a timing function, returns the transformed variant
function makeEaseOut(timing) {
return function(timeFraction) {
return 1 - timing(1 - timeFraction);
}
}
Na primer, možemo da uzmemo bounce f-ju opisanu iznad i da je primenimo:
let bounceEaseOut = makeEaseOut(bounce);
Onda odskakanje neće biti na početku već na kraju.
Pogledajte primer koda:
html:
<!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, result; 1; a += b, b /= 2) {

13
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>

css kod:
#brick {
width: 40px;
height: 20px;
background: #EE6B47;
position: relative;
cursor: pointer;
}

#path {
outline: 1px solid #E8C48E;
width: 540px;
height: 20px;
}

Ovde možemo videti kako transformacija menja ponašanje f-je:

14
Ukoliko postoji efekat animacije na početku kao što je odskakanje – biće prikazano na kraju.
U grafiku iznad regularno odskakanje je crveno, a easeOut odskakanje je plavo.
 Regularno odskakanje – objekat odskače na dnu, onda na kraju oštro odskače na vrh
 Nakon easeOut – prvo skače na vrh, i onda odskače gore.

easeInOut
Takođe možemo da pokažemo efekat i na početku i na kraju animacije. Transformacija se zove
“easeInOut”.
Sa datom vremenskom f-jom računamo stanje animacije ovako:
if (timeFraction <= 0.5) { // first half of the animation
return timing(2 * timeFraction) / 2;
} else { // second half of the animation
return (2 - timing(2 * (1 - timeFraction))) / 2;
}

Kod omotača:
function makeEaseInOut(timing) {
return function(timeFraction) {
if (timeFraction < .5)
return timing(2 * timeFraction) / 2;
else

15
return (2 - timing(2 * (1 - timeFraction))) / 2;
}
}

bounceEaseInOut = makeEaseInOut(bounce);

U akciji bounceEaseInOut:
html:
<!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, result; 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';
}
});
};

16
</script>

</body>

</html>

css
#brick {
width: 40px;
height: 20px;
background: #EE6B47;
position: relative;
cursor: pointer;
}

#path {
outline: 1px solid #E8C48E;
width: 540px;
height: 20px;
}

easeInOut transformacija spaja dva grafikona u jedan: easeIn za prvi deo animacije i easeOut za
drugi deo.
Efekat se jasno vidi ako uporedimo grafikone za easeIn, easeOut i easeInOut circ f-je:

17
 Crvena je regularna varijanta circ (easeIn).
 Zelena – easeOut
 Plava – easeInOut

draw
Umesto da pomeramo element možemo da uradimo nešto drugo. Sve što treba da uradimo jeste
da napišemo pravilan draw.
Evo animiranog 'bouncing' text kucanja:
html:
<!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="textExample" 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="animateText(textExample)">Run the animated typing!</button>

<script>
function animateText(textArea) {
let text = textArea.value;
let to = text.length,
from = 0;

animate({
duration: 5000,
timing: bounce,
draw: function(progress) {
let result = (to - from) * progress + from;
textArea.value = text.substr(0, Math.ceil(result))
}
});
}

function bounce(timeFraction) {
for (let a = 0, b = 1, result; 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)

18
}
}
}
</script>

</body>

</html>

css
textarea {
display: block;
border: 1px solid #BBB;
color: #444;
font-size: 110%;
}

button {
margin-top: 10px;
}

19

You might also like