آموزش جاوا اسکریپت – جلسه ۱۴ – انیمیشن Animation

در جلسه سیزدهم از سری آموزشی جاوا اسکریپت، درباره دو آبجکت ذخیره سازی وب استوریج بنام های Local-Storage و Session-Storage صحبت کردیم. در این جلسه می خواهیم مبحث انیمیشن ها در جاوا اسکریپت را آموزش دهیم.
با استفاده از انیمیشن های جاوا اسکریپت می توان کارهایی انجام داد که توسط استایل های CSS نمی توان انجام داد. بعنوان مثال حرکت در طول یک مسیر پیچیده با توابع زمانبندی یا اجرای انیمیشن روی یک Canvas.
استفاده از setInterval:
یک انیمیشن می تواند بصورت توالی چند فریم رخ دهد که با تغییراتی ناچیز در کدهای HTML/CSS همراه خواهد بود. بعنوان مثال تغییر خاصیت left المان از 0px به 100px. حالا اگر یک جابجایی دو پیکسلی را در فواصل زمانی بسیار کم (مثلا 50 بار در هر ثانیه) تنظیم کنیم، یک انیمیشن نرم (smooth) خواهیم داشت.
در سینما نیز از همین ترفند برای نمایش تصاویر بر روی پرده سینما استفاده می شود. در هر ثانیه 24 فریم.
کد الگوریتمی آن بصورت زیر خواهد بود:
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
اما کد واقعی و کامل آن می تواند به شکل زیر باشد:
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'; }
برای مشاهده دموی انیمیشن فوق به این لینک مراجعه کنید.
استفاده از requestAnimationFrame:
فرض کنید چندین انیمیشن داریم که بطور همزمان در حال اجرا هستند. اگر آنها را بصورت جداگانه اجرا کنیم، هریک از آنها مثلا شامل setInterval(…, 20) خواهند بود که هر 20 ثانیه یکبار انیمیشن را تکرار می کند. به این دلیل که زمان شروع هر یک از انیمیشن ها متفاوت است فاصله زمانی شروع و پایان آنها هم با یکدیگر فرق خواهد داشت و نامنظم اجرا خواهند شد.
به بیان دیگر:
setInterval(function() { animate1(); animate2(); animate3(); }, 20)
قطعه کد بالا ورژن سبک تر و چابک تر کد زیر است:
setInterval(animate1, 20); // independent animations setInterval(animate2, 20); // in different places of the script setInterval(animate3, 20);
یعنی اگر انیمیشن ها را بصورت جداگانه و مستقل تعریف کنیم کند تر از حالت اول اجرا خواهند شد. بنابراین برای بهبود کارایی انیمیشن ها توصیه می شود این سه انیمیشن مستقل را در یک دستور گروه بندی کنید.
در مواقعی که CPU بسیار مشغول است یا تب مرورگر فعال نیست اجرای انیمیشن های فوق ادامه پیدا می کنند. در حالیکه بهتر است انیمیشن ها در این مواقع متوقف شوند. اما مدیریت اجرای انیمیشن ها را چگونه می توان در جاوا اسکریپت مدیریت کرد؟ خوشبختانه تابع requestAnimationFrame برای اینکار وجود دارد.
فرمت دستور:
let requestId = requestAnimationFrame(callback)
کد بالا تابع callback را طوری زمان بندی می کند که به محض اینکه مرورگر می خواهد انیمیشن را اجرا کند اجر شود. اگر ما تغییراتی در المان های تابع کال بک اعمال کنیم آنها با سایر کال بک های تابع requestAnimationFrame و انیمیشن های CSS گروه بندی می شوند. در این حالت اجرای انیمیشن های JS و CSS هماهنگ می شوند و یکبار محاسبه می شوند.
از requestId نیز می توان برای کنسل یا لغو کردن فراخوانی انیمیشن استفاده کرد:
// cancel the scheduled execution of callback cancelAnimationFrame(requestId);
تابع callback یک آرگومان ورودی می گیرد. مقدار زمانی که از شروع لود صفحه گذشته است و واحد آن میلی ثانیه است. معمولا تابع call back خیلی سریع اجرا می شود مگر اینکه CPU بسیار مشغول باشد یا باتری لپ تاپ به انتها رسیده باشد یا به دلایل مشابه دیگر…
کد زیر فاصله زمانی بین 10 اجرای اول برای requestAnimationFrame را نمایش می دهد. معمولا بین 10 تا 20 میلی ثانیه است:
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); })
انیمیشن ساختاریافته (Structured Animation):
حالا ما می توانیم یک تابع انیمیشن کلی بر اساس 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) { requestAnimationFrame(animate); } }); }
تابع animation سه پارامتر می گیرد که مشخص کننده چگونگی اجرای انیمیشن هستند:
Duration:
زمان کلی انیمیشن. مثلا 1000 میلی ثانیه.
timing(timeFraction):
توابع زمانبندی مانند خاصیت سی اس اس transition-timing-function که بخشی از زمانی که گذشته است (عدد 0 برای شروع و عدد 1 برای پایان انیمیشن) را می گیرد و انیمیشن را به خروجی می دهد.
بعنوان مثال، یک تابع خطی یا linear به این معنیست که انیمیشن در تمام زمان اجرا با سرعت ثابت و یکسان اجرا می شود.
function linear(timeFraction) { return timeFraction; }
و گراف آن:
مانند اینست که بنویسیم:
transition-timing-function: linear
draw(progress):
تابع draw آرگومان ورودی progress را می گیرد که progress=0 بیانگر شروع انیمیشن و progress=1 معرف پایان انیمیشن است.
در کد زیر تابع draw یک المان را حرکت می دهد:
function draw(progress) { train.style.left = progress + 'px'; }
مثال: می خواهیم یک انیمیشن در جاوا اسکریپت تعریف کنیم که در عرض از 0 تا 100% حرکت می کند.
کد js:
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); } }); }
کد html:
<!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>
برای مشاهده دموی انیمیشن کلیک کنید.
کد انیمیشن:
animate({ duration: 1000, timing(timeFraction) { return timeFraction; }, draw(progress) { elem.style.width = progress * 100 + '%'; } });
بر خلاف انیمیشن های css ما در تعریف انیمیشن توسط کد بالا هیچ محدودیتی نداریم و می توان از انواع توابع timing در آن استفاده کرد.
توابع Timing:
در این بخش می خواهیم انیمیشن های حرکتی را با توابع تایمینگ متفاوت تست کنیم تا ببینیم هر یک از آنها چگونه کار می کنند.
به توان n:
اگر بخواهیم سرعت اجرای انیمیشن را زیاد کنیم می توان از توان n استفاده کرد:
بعنوان مثال:
function quad(timeFraction) { return Math.pow(timeFraction, 2) }
گراف آن:
برای مشاهده دموی انیمیشن کلیک کنید.
آرک (Arc):
تابع زیر:
function circ(timeFraction) { return 1 - Math.sin(Math.acos(timeFraction)); }
مشاهده دموی انیمیشن
انیمیشن back » تیراندازی با کمان (bow shooting):
این تابع شبیه رها شدن تیر از کمان اجرا می شود. بصورتی که ابتدا کمان کمی به عقب کشیده می شود و سپس تیر با شتاب از کمان رها می شود. مانند توابع قبلی، به یک پارامتر دیگر بنام x بستگی دارد. X بیانگر مقداری است که کمان به عقب کشیده می شود.
کد تابع back:
function back(x, timeFraction) { return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x) }
گراف انیمیشن back با x=1.5 :
مشاهده دموی انیمیشن back
انیمیشن Bounce:
تصور کنید یک توپ تنیس را به روی زمین بیاندازید. این توپ پس از چند بار زمین خوردن و به بالا برگشتن متوقف می شود. رفتار انیمیشن Bounce هم به همین شکل است اما برعکس. یعنی عمل bounce شدن انیمیشن ابتدا سریع انجام می شود و بتدریج از سرعتش کاسته می شود.
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) } } }
مشاهده دموی انیمیشن
انیمیشن Elastic:
تابع elastic یک پارامتر x دیگر می گیرد که معین کننده محدوده اولیه انیمیشن یا initial range می باشد.
گراف انیمیشن الستیک:
مشاهده دموی انیمیشن
افکت های ease:
در این بخش مجموعه ای ازتوابع زمانبندی یا timing را معرفی خواهیم کرد. پیشفرض آنها easeIn است.
افکت easeOut:
گاهی اوقات می خواهیم انیمیشن را بطور برعکس اجرا کنیم. در اینگونه موارد از easeOut استفاده می کنیم. در این حالت تابع timing در یک wrapper بنام timingEaseOut قرار می گیرد.
کد زیر:
timingEaseOut(timeFraction) = 1 - timing(1 - timeFraction)
به بیان دیگر، یک تابع ترنسفرم بنام makeEaseOut داریم که یک تابع timing معمولی بعنوان ورودی می گیرد و آن را بهمراه یک wrapper در اطراف آن به خروجی می دهد:
// accepts a timing function, returns the transformed variant function makeEaseOut(timing) { return function(timeFraction) { return 1 - timing(1 - timeFraction); } }
برای نمونه می توان تابع bounce را به عنوان ورودی آن ارسال کرد:
let bounceEaseOut = makeEaseOut(bounce);
در اینصورت عمل bounce بصورت برعکس اجرا می شود یعنی در ابتدا با سرعت کم شروع می شود و در نهایت با سرعت بیشتر خاتمه می یابد.
مشاهده دموی افکت
در گراف زیر، نشان دادیم که عملیات transform چگونه رفتار یک انیمیشن را تغییر می دهد. نمودار قرمز رنگ بیانگر انیمیشن bounce در حالت پیش فرض است و نمودار آبی رنگ بیانگر انیمیشن bounce بصورت easeOut می باشد.
افکت easeInOut:
ما همچنین می توانیم افکت موردنظر را به ابتدا و انتهای اجرای انیمیشن اعمال کنیم. این نوع transform نامش easeInOut می باشد.
کد زیر:
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; }
کد wrapper:
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);
مشاهده دموی افکت
افکت easeInOut ترکیبی از دو افکت easeIn (اجرای انیمیشن در نیمه اول) و easeOut (اجرای انیمیشن در نیمه دوم) است.
در گراف زیر سه افکت easeIn و easeOut و easeInOut را نمایش داده ایم:
- نمودار قرمز: اجرای انیمیشن با افکت easeIn
- نمودار سبز: اجرای انیمیشن با افکت easeOut
- نمودار آبی: اجرای انیمیشن با افکت easeInOut
افکت تایپ متن:
برای مثال می خواهیم افکت bounce را روی یک متن در حال تایپ (Typing Text) اعمال کنیم. به این منظور باید از draw به شکل زیر استفاده شود:
کد html:
<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>
کد CSS:
textarea { display: block; border: 1px solid #BBB; color: #444; font-size: 110%; } button { margin-top: 10px; }
و کد JS:
<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>
مشاهده دموی انیمیشن “متن در حال تایپ”
برای مطالعه جلسه پانزدهم آموزش جاوا اسکریپت (درباره استایل نویسی CSS در javascript) کلیک کنید.
دیدگاهتان را بنویسید