JavaScript animatsiyalari CSS bilan amalga oshirib bo’lmaydigan narsalarni boshqarishi mumkin.
Masalan, murakkab yo’l bo’ylab harakat qilish, Bezier egri chiziqlaridan farqli timing funksiyasi bilan yoki canvas ustida animatsiya.
setInterval dan foydalanish
Animatsiyani kadrlar ketma-ketligi sifatida amalga oshirish mumkin – odatda HTML/CSS xususiyatlarining kichik o’zgarishlari.
Masalan, style.left
ni 0px
dan 100px
gacha o’zgartirish elementni harakatga keltiradi. Va agar biz uni setInterval
da oshirsak, sekundiga taxminan 50 marta 2px ga kichik kechikish bilan o’zgartirsak, u silliq ko’rinadi. Bu kinematografiya bilan bir xil printsip: sekundiga 24 kadr silliq ko’rinish uchun etarli.
Psevdo-kod quyidagicha ko’rinishi mumkin:
let timer = setInterval(function() {
if (animation complete) clearInterval(timer);
else increase style.left by 2px
}, 20); // har 20ms da 2px ga o'zgartirish, sekundiga taxminan 50 kadr
Animatsiyaning to’liqroq misoli:
let start = Date.now(); // boshlanish vaqtini eslab qolish
let timer = setInterval(function() {
// boshlanishdan qancha vaqt o'tdi?
let timePassed = Date.now() - start;
if (timePassed >= 2000) {
clearInterval(timer); // 2 soniyadan keyin animatsiyani tugatish
return;
}
// timePassed momentida animatsiyani chizish
draw(timePassed);
}, 20);
// timePassed 0 dan 2000 gacha o'tganda
// left qiymatlari 0px dan 400px gacha oladi
function draw(timePassed) {
train.style.left = timePassed / 5 + 'px';
}
Demo uchun bosing:
<!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>
requestAnimationFrame dan foydalanish
Faraz qilaylik, bizda bir vaqtning o’zida bir nechta animatsiyalar ishlayapti.
Agar biz ularni alohida ishga tushirsak, har birida setInterval(..., 20)
bo’lsa ham, brauzer har 20ms
dan ko’ra tez-tez qayta chizishga majbur bo’ladi.
Buning sababi ular turli boshlanish vaqtiga ega, shuning uchun “har 20ms” turli animatsiyalar o’rtasida farq qiladi. Intervallar bir xil emas. Shunday qilib, 20ms
ichida bir nechta mustaqil ishga tushirish bo’ladi.
Boshqacha qilib aytganda, bu:
setInterval(function() {
animate1();
animate2();
animate3();
}, 20)
…Uchta mustaqil chaqiruvdan yengilroq:
setInterval(animate1, 20); // mustaqil animatsiyalar
setInterval(animate2, 20); // skriptning turli joylarida
setInterval(animate3, 20);
Bu bir nechta mustaqil qayta chizishlar birlashtirilishi kerak, brauzer uchun qayta chizishni osonlashtirish va shu bilan CPU yukini kamaytirish va silliqroq ko’rinish uchun.
Yodda tutish kerak bo’lgan yana bir narsa bor. Ba’zida CPU yuklanadi yoki kamroq tez qayta chizish uchun boshqa sabablar bor (masalan, brauzer yorlig’i yashiringanda), shuning uchun biz uni har 20ms
da ishlatmasligimiz kerak.
Lekin buni JavaScript-da qanday bilamiz? Animation timing spetsifikatsiyasi mavjud bo’lib, u requestAnimationFrame
funksiyasini taqdim etadi. U bu barcha masalalarni va undan ham ko’pini hal qiladi.
Sintaksis:
let requestId = requestAnimationFrame(callback)
Bu brauzer animatsiya qilmoqchi bo’lgan eng yaqin vaqtda callback
funksiyasini ishga tushirishni rejalashtiradi.
Agar biz callback
da elementlarda o’zgarishlar qilsak, ular boshqa requestAnimationFrame
callback-lari va CSS animatsiyalari bilan birlashtiriladi. Shunday qilib, ko’p marta emas, balki bitta geometriya qayta hisoblanishi va qayta chizilishi bo’ladi.
Qaytarilgan qiymat requestId
chaqiruvni bekor qilish uchun ishlatilishi mumkin:
// callback-ning rejalashtirilgan bajarilishini bekor qilish
cancelAnimationFrame(requestId);
callback
bitta argumentni oladi – sahifa yuklanishining boshlanishidan o’tgan vaqt mikrosoniyalarda. Bu vaqtni performance.now() ni chaqirish orqali ham olish mumkin.
Odatda callback
juda tez ishlaydi, CPU yuklanmagan yoki noutbukning batareyasi deyarli tugamagan yoki boshqa sabab bo’lmasa.
Quyidagi kod requestAnimationFrame
uchun dastlabki 10 ta ishga tushirish orasidagi vaqtni ko’rsatadi. Odatda bu 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>
Tuzilgan animatsiya
Endi biz requestAnimationFrame
asosida universal animatsiya funksiyasini yaratishimiz mumkin:
function animate({timing, draw, duration}) {
let start = performance.now();
requestAnimationFrame(function animate(time) {
// timeFraction 0 dan 1 gacha
let timeFraction = (time - start) / duration;
if (timeFraction > 1) timeFraction = 1;
// joriy animatsiya holatini hisoblab chiqish
let progress = timing(timeFraction)
draw(progress); // uni chizish
if (timeFraction < 1) {
requestAnimationFrame(animate);
}
});
}
animate
funksiyasi animatsiyani asosan tavsiflayotgan 3ta parametrni qabul qiladi:
duration
-
Animatsiyaning umumiy vaqti. Masalan,
1000
. timing(timeFraction)
-
Timing funksiyasi, CSS-xususiyati
transition-timing-function
kabi, o’tgan vaqtning qismini (boshlanishda 0
,oxirida 1
) oladi va animatsiya tugallanishini qaytaradi (Bezier egri chizig’idagiy
kabi).Masalan, chiziqli funksiya animatsiya bir xil tezlik bilan bir tekis davom etishini bildiradi:
function linear(timeFraction) { return timeFraction; }
Uning grafigi:
Bu
transition-timing-function: linear
kabi. Quyida ko’rsatilgan qiziqarli variantlar bor. draw(progress)
-
Animatsiya tugallanish holatini oladigan va uni chizadigan funksiya.
progress=0
qiymati boshlang’ich animatsiya holatini,progress=1
esa oxirgi holatni bildiradi.Bu animatsiyani haqiqatan ham chizadigan funksiya.
U elementni harakatga keltirishi mumkin:
function draw(progress) { train.style.left = progress + 'px'; }
…Yoki boshqa istalgan narsani qilishi mumkin, biz har qanday narsani, har qanday usulda animatsiya qilishimiz mumkin.
Keling, funksiyamizdan foydalanib element width
ini 0
dan 100%
gacha animatsiya qilaylik.
Demo uchun elementga bosing:
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>
Uning kodi:
animate({
duration: 1000,
timing(timeFraction) {
return timeFraction;
},
draw(progress) {
elem.style.width = progress * 100 + '%';
}
});
CSS animatsiyasidan farqli o’laroq, biz bu yerda har qanday timing funksiyasi va har qanday chizish funksiyasini yaratishimiz mumkin. Timing funksiyasi Bezier egri chiziqlari bilan cheklanmagan. Va draw
xususiyatlardan tashqariga chiqishi mumkin, masalan, otashin animatsiyasi yoki shunga o’xshash narsalar uchun yangi elementlar yaratishi mumkin.
Timing funksiyalari
Biz yuqorida eng oddiy, chiziqli timing funksiyasini ko’rdik.
Keling, ulardan ko’proqini ko’raylik. Biz ular qanday ishlashini ko’rish uchun turli timing funksiyalari bilan harakat animatsiyalarini sinab ko’ramiz.
n darajasi
Agar biz animatsiyani tezlashtirmoqchi bo’lsak, progress
ni n
darajasida ishlatishimiz mumkin.
Masalan, parabola egri chizig’i:
function quad(timeFraction) {
return Math.pow(timeFraction, 2)
}
Grafik:
Harakatda ko’ring (faollashtirish uchun bosing):
…Yoki kub egri chizig’i yoki undan ham katta n
. Darajani oshirish tezroq harakat qilishga olib keladi.
Mana progress
ning 5
darajasidagi grafigi:
Harakatda:
Yoy
Funksiya:
function circ(timeFraction) {
return 1 - Math.sin(Math.acos(timeFraction));
}
Grafik:
Orqaga: kamon otish
Bu funksiya "kamon otish"ni amalga oshiradi. Avval biz “kamon torini tortamiz”, keyin “otamiz”.
Oldingi funksiyalardan farqli o’laroq, u qo’shimcha parametr x
– "elastiklik koeffitsienti"ga bog’liq. “Kamon torini tortish” masofasi uni belgilaydi.
Kod:
function back(x, timeFraction) {
return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x)
}
x = 1.5
uchun grafik:
Animatsiya uchun biz uni x
ning aniq qiymati bilan ishlatamiz. x = 1.5
uchun misol:
Sakrash
Tasavvur qiling, biz to’pni tashlaymiz. U pastga tushadi, keyin bir necha marta sakrab qaytadi va to’xtaydi.
bounce
funksiyasi xuddi shunday qiladi, lekin teskari tartibda: “sakrash” darhol boshlanadi. U buning uchun bir nechta maxsus koeffitsientlardan foydalanadi:
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)
}
}
}
Harakatda:
Elastik animatsiya
“Boshlang’ich diapazon” uchun qo’shimcha parametr x
ni qabul qiladigan yana bitta “elastik” funksiya.
function elastic(x, timeFraction) {
return Math.pow(2, 10 * (timeFraction - 1)) * Math.cos(20 * Math.PI * x / 3 * timeFraction)
}
x=1.5
uchun grafik:
x=1.5
uchun harakatda:
Teskari: ease*
Shunday qilib, bizda timing funksiyalari to’plami bor. Ularning to’g’ridan-to’g’ri qo’llanilishi “easeIn” deb ataladi.
Ba’zan biz animatsiyani teskari tartibda ko’rsatishimiz kerak. Bu “easeOut” transformatsiyasi bilan amalga oshiriladi.
easeOut
“easeOut” rejimida timing
funksiyasi timingEaseOut
wrapper-ga o’raladi:
timingEaseOut(timeFraction) = 1 - timing(1 - timeFraction)
Boshqacha qilib aytganda, bizda “oddiy” timing funksiyasini oladigan va uning atrofida wrapper qaytaradigan “transform” funksiyasi makeEaseOut
bor:
// timing funksiyasini qabul qiladi, o'zgartirilgan variantni qaytaradi
function makeEaseOut(timing) {
return function(timeFraction) {
return 1 - timing(1 - timeFraction);
}
}
Masalan, biz yuqorida tasvirlangan bounce
funksiyasini olib uni qo’llashimiz mumkin:
let bounceEaseOut = makeEaseOut(bounce);
Keyin sakrash animatsiyaning boshida emas, balki oxirida bo’ladi. Yanada yaxshi ko’rinadi:
#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, 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 bounceEaseOut = makeEaseOut(bounce);
brick.onclick = function () {
animate({
duration: 3000,
timing: bounceEaseOut,
draw: function (progress) {
brick.style.left = progress * 500 + "px";
},
});
};
</script>
</body>
</html>
Bu yerda biz transformatsiya funksiya harakatini qanday o’zgartirishini ko’rishimiz mumkin:
Agar boshida animatsiya effekti bo’lsa, masalan, sakrash – u oxirida ko’rsatiladi.
Yuqoridagi grafikda oddiy bounce qizil rang bilan, easeOut bounce esa ko’k rang bilan.
- Oddiy bounce – ob’ekt pastda sakraydi, keyin oxirida keskin tepaga sakraydi.
easeOut
dan keyin – u avval tepaga sakraydi, keyin u yerda sakraydi.
easeInOut
Shuningdek, biz effektni animatsiyaning boshida ham, oxirida ham ko’rsatishimiz mumkin. Bu transformatsiya “easeInOut” deb ataladi.
Timing funksiyasi berilgan holda, biz animatsiya holatini quyidagicha hisoblaymiz:
if (timeFraction <= 0.5) { // animatsiyaning birinchi yarmi
return timing(2 * timeFraction) / 2;
} else { // animatsiyaning ikkinchi yarmi
return (2 - timing(2 * (1 - timeFraction))) / 2;
}
Wrapper kodi:
function makeEaseInOut(timing) {
return function(timeFraction) {
if (timeFraction < .5)
return timing(2 * timeFraction) / 2;
else
return (2 - timing(2 * (1 - timeFraction))) / 2;
}
}
bounceEaseInOut = makeEaseInOut(bounce);
Harakatda, bounceEaseInOut
:
#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 < 0.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";
},
});
};
</script>
</body>
</html>
“easeInOut” transformatsiyasi ikkita grafikni birlashtiradi: animatsiyaning birinchi yarmi uchun easeIn
(oddiy) va ikkinchi qism uchun easeOut
(teskari).
Agar biz circ
timing funksiyasining easeIn
, easeOut
va easeInOut
grafiklarini solishtirsak, effekt aniq ko’rinadi:
- Qizil –
circ
(easeIn
) ning oddiy varianti. - Yashil –
easeOut
. - Ko’k –
easeInOut
.
Ko’rib turganingizdek, animatsiyaning birinchi yarmining grafigi kichraytirilgan easeIn
, ikkinchi yarmi esa kichraytirilgan easeOut
. Natijada, animatsiya bir xil effekt bilan boshlanadi va tugaydi.
Qiziqarli “draw”
Elementni harakatga keltirish o’rniga boshqa narsani qilishimiz mumkin. Bizga kerak bo’lgan narsa – to’g’ri draw
yozish.
Mana animatsiyalangan “sakrash” matn yozishi:
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="textExample" rows="5" cols="60">
U vorpal qilichini qo'liga oldi:
Uzoq vaqt davomida u manxome dushmanini qidirdi -
Shunday qilib, u Tumtum daraxti yonida dam oldi,
Va bir muddat o'ylanib qoldi.
</textarea
>
<button onclick="animateText(textExample)">
Animatsion yozishni ishga tushiring!
</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)
);
}
}
}
</script>
</body>
</html>
Xulosa
CSS yaxshi boshqara olmaydigan animatsiyalar yoki qat’iy nazorat kerak bo’lganlar uchun JavaScript yordam berishi mumkin. JavaScript animatsiyalari requestAnimationFrame
orqali amalga oshirilishi kerak. Bu o’rnatilgan metod brauzer qayta chizishga tayyorlanayotganda callback funksiyasini ishga tushirish imkonini beradi. Odatda bu juda tez, lekin aniq vaqt brauzerga bog’liq.
Sahifa fonda bo’lganda, qayta chizishlar umuman bo’lmaydi, shuning uchun callback ishlamaydi: animatsiya to’xtatiladi va resurslarni iste’mol qilmaydi. Bu ajoyib.
Mana ko’pchilik animatsiyalarni sozlash uchun yordamchi animate
funksiyasi:
function animate({timing, draw, duration}) {
let start = performance.now();
requestAnimationFrame(function animate(time) {
// timeFraction 0 dan 1 gacha
let timeFraction = (time - start) / duration;
if (timeFraction > 1) timeFraction = 1;
// joriy animatsiya holatini hisoblash
let progress = timing(timeFraction);
draw(progress); // uni chizish
if (timeFraction < 1) {
requestAnimationFrame(animate);
}
});
}
Opsiyalar:
duration
– ms da umumiy animatsiya vaqti.timing
– animatsiya jarayonini hisoblash funksiyasi. 0 dan 1 gacha vaqt qismini oladi, animatsiya jarayonini qaytaradi, odatda 0 dan 1 gacha.draw
– animatsiyani chizish funksiyasi.
Albatta biz uni yaxshilashimiz, ko’proq xususiyatlar qo’shishimiz mumkin, lekin JavaScript animatsiyalari har kuni qo’llanilmaydi. Ular qiziqarli va nostandart narsalar qilish uchun ishlatiladi. Shuning uchun siz kerak bo’lganda kerakli xususiyatlarni qo’shmoqchi bo’lasiz.
JavaScript animatsiyalari har qanday timing funksiyasidan foydalanishi mumkin. Biz ularni yanada ko’p qirrali qilish uchun ko’plab misollar va transformatsiyalarni ko’rib chiqdik. CSS dan farqli o’laroq, biz bu yerda Bezier egri chiziqlari bilan cheklanmaymiz.
draw
haqida ham xuddi shunday: biz CSS xususiyatlari emas, balki har qanday narsani animatsiya qilishimiz mumkin.
Izohlar
<code>
yorlig'ini ishlating, bir nechta satrlar uchun - ularni<pre>
yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepen…)