Professional Documents
Culture Documents
JS Animacije
JS Animacije
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
}, 20);
1
Rezultat izgleda ovako:
<head>
<style>
#train {
position: relative;
cursor: pointer;
}
</style>
</head>
<body>
<script>
train.onclick = function() {
let start = Date.now();
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;
Strukturisana animacija
Sada možemo da pravimo univerzalniju f-ju animacije baziranu na requestAnimationFrame:
function animate({timing, draw, duration}) {
requestAnimationFrame(function animate(time) {
// timeFraction goes from 0 to 1
let timeFraction = (time - start) / duration;
if (timeFraction > 1) timeFraction = 1;
draw(progress); // draw it
if (timeFraction < 1) {
4
requestAnimationFrame(animate);
}
});
}
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}) {
requestAnimationFrame(function animate(time) {
let timeFraction = (time - start) / duration;
if (timeFraction > 1) timeFraction = 1;
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)
}
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)
}
}
}
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;
}
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)
}
}
}
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>
<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