مقدمه
میخواهید برنامه وب شما هنگام انجام انیمیشنها، انتقالها و سایر جلوههای رابط کاربری کوچک، پاسخگو و روان باشد. اطمینان از اینکه این افکتها بدون jank هستند، میتواند به معنای تفاوت بین یک احساس «بومی» یا یک حس بد و بینقص باشد.
این اولین مقاله از سری مقالاتی است که بهینه سازی عملکرد رندر در مرورگر را پوشش می دهد. برای شروع کار، به این موضوع می پردازیم که چرا انیمیشن روان دشوار است و برای دستیابی به آن چه اتفاقی باید بیفتد، و همچنین چند بهترین روش آسان. بسیاری از این ایدهها در اصل در «Jank Busters» ارائه شد، سخنرانی من و نات دوکا در سخنرانی Google I/O ( ویدئو ) امسال.
معرفی V-sync
گیمرهای رایانه شخصی ممکن است با این اصطلاح آشنا باشند، اما در وب غیر معمول است: v-sync چیست؟
صفحه نمایش تلفن خود را در نظر بگیرید: در یک بازه زمانی منظم، معمولا (اما نه همیشه!) حدود 60 بار در ثانیه به روز می شود. V-sync (یا همگامسازی عمودی) به تمرین تولید فریمهای جدید فقط در بین تازهسازی صفحه اشاره دارد. ممکن است این مورد را مانند شرایط مسابقه بین فرآیندی که دادهها را در بافر صفحه نمایش مینویسد و سیستمعاملی که آن دادهها را برای قرار دادن آن روی نمایشگر میخواند، در نظر بگیرید. ما میخواهیم محتویات فریم بافر در بین این تازهسازیها تغییر کند، نه در طول آنها. در غیر این صورت مانیتور نیمی از یک فریم و نیمی از فریم دیگر را نمایش می دهد که منجر به " پارگی " می شود.
برای به دست آوردن یک انیمیشن روان، به یک فریم جدید نیاز دارید تا هر بار که به روز رسانی صفحه رخ می دهد، آماده باشد. این دو پیامد بزرگ دارد: زمان بندی فریم (یعنی زمانی که فریم باید آماده شود) و بودجه فریم (یعنی مدت زمانی که مرورگر باید یک فریم تولید کند). شما فقط بین تازه کردن صفحه برای تکمیل یک فریم زمان دارید (~16 میلی ثانیه در صفحه 60 هرتز)، و می خواهید به محض اینکه آخرین فریم روی صفحه نمایش داده شد، تولید فریم بعدی را شروع کنید.
زمان همه چیز است: requestAnimationFrame
بسیاری از توسعه دهندگان وب از setInterval
یا setTimeout
هر 16 میلی ثانیه برای ایجاد انیمیشن استفاده می کنند. این یک مشکل به دلایل مختلفی است (و ما در یک دقیقه بیشتر در مورد آن صحبت خواهیم کرد)، اما نگرانی خاصی عبارتند از:
- وضوح تایمر از جاوا اسکریپت فقط در حد چند میلی ثانیه است
- دستگاه های مختلف نرخ تازه سازی متفاوتی دارند
مشکل زمان بندی فریم که در بالا ذکر شد را به خاطر بیاورید: شما نیاز به یک قاب انیمیشن کامل دارید که با هر جاوا اسکریپت، دستکاری DOM، طرح بندی، نقاشی و غیره تمام شده باشد تا قبل از بروز رسانی صفحه بعدی آماده شود. رزولوشن پایین تایمر میتواند تکمیل فریمهای انیمیشن را قبل از بهروزرسانی بعدی صفحه دشوار کند، اما تغییر در نرخهای تازهسازی صفحه این کار را با یک تایمر ثابت غیرممکن میکند. مهم نیست که فاصله زمانی تایمر چقدر باشد، شما به آرامی از پنجره زمان بندی خارج می شوید و یک فریم را رها می کنید. این اتفاق میافتد حتی اگر تایمر با دقت میلیثانیه شلیک شود، که این کار را نمیکند (همانطور که توسعهدهندگان کشف کردهاند ) - وضوح تایمر بسته به اینکه دستگاه روی باتری است یا به برق متصل است، متفاوت است، میتواند تحت تأثیر منابع ذخیرهسازی برگههای پسزمینه قرار گیرد. حتی اگر این نادر باشد (مثلاً هر 16 فریم به دلیل اینکه یک میلی ثانیه خاموش بودید) متوجه خواهید شد: در هر ثانیه چندین فریم رها می کنید. شما همچنین برای تولید فریمهایی که هرگز نمایش داده نمیشوند، کار میکنید، که باعث هدر رفتن انرژی و زمان CPU شما میشود که میتوانید برای انجام کارهای دیگر در برنامه خود صرف کنید.
نمایشگرهای مختلف دارای نرخ رفرش متفاوت هستند: 60 هرتز معمول است، اما برخی از تلفن ها 59 هرتز هستند، برخی از لپ تاپ ها در حالت کم مصرف به 50 هرتز کاهش می یابند، برخی از مانیتورهای دسکتاپ 70 هرتز هستند.
ما تمایل داریم هنگام بحث در مورد عملکرد رندر روی فریم در ثانیه (FPS) تمرکز کنیم، اما واریانس میتواند مشکل بزرگتری باشد. چشمان ما متوجه ضربه های ریز و نامنظم در انیمیشن می شوند که یک انیمیشن با زمان بندی ضعیف می تواند ایجاد کند.
راه برای بدست آوردن فریم های انیمیشن زمان بندی شده با requestAnimationFrame
است. وقتی از این API استفاده می کنید، از مرورگر یک قاب انیمیشن می خواهید. هنگامی که مرورگر به زودی فریم جدیدی تولید می کند، تماس برگشتی شما فراخوانی می شود. این اتفاق می افتد صرف نظر از نرخ تجدید.
requestAnimationFrame
ویژگی های خوب دیگری نیز دارد:
- انیمیشنها در برگههای پسزمینه متوقف میشوند و منابع سیستم و عمر باتری حفظ میشوند.
- اگر سیستم نتواند رندر را با نرخ تازهسازی صفحهنمایش انجام دهد، میتواند انیمیشنها را کاهش دهد و تماسهای کمتری را تولید کند (مثلاً 30 بار در ثانیه روی صفحهنمایش 60 هرتز). در حالی که این نرخ فریم را به نصف کاهش می دهد، انیمیشن را ثابت نگه می دارد - و همانطور که در بالا گفته شد، چشمان ما بسیار بیشتر از نرخ فریم با واریانس هماهنگ است. 30 هرتز ثابت بهتر از 60 هرتز است که چند فریم در ثانیه را از دست می دهد.
requestAnimationFrame
قبلاً در همه جا مورد بحث قرار گرفته است، بنابراین برای اطلاعات بیشتر در مورد آن به مقالاتی مانند این از JS خلاق مراجعه کنید، اما این اولین قدم مهم برای روان سازی انیمیشن است.
بودجه چارچوبی
از آنجایی که میخواهیم یک فریم جدید در هر بازخوانی صفحه آماده باشد، تنها زمانی بین بازخوانیها وجود دارد تا همه کارها برای ایجاد یک فریم جدید انجام شود. در یک صفحه نمایش 60 هرتزی، این بدان معناست که ما حدود 16 میلیثانیه برای اجرای تمام جاوا اسکریپت، اجرای طرحبندی، رنگآمیزی و هر کار دیگری که مرورگر برای بیرون آوردن فریم باید انجام دهد، داریم. این بدان معناست که اگر جاوا اسکریپت در داخل requestAnimationFrame
فراخوانی شما بیش از 16 میلیثانیه طول بکشد، هیچ امیدی به تولید فریم به موقع برای v-sync ندارید!
16 میلیثانیه زمان زیادی نیست. خوشبختانه ابزارهای برنامهنویس کروم میتوانند به شما کمک کنند اگر بودجه فریم خود را در طول پاسخ به درخواست AnimationFrame کاهش میدهید، ردیابی کنید.
باز کردن جدول زمانی Dev Tools و ضبط کردن این انیمیشن در عمل به سرعت نشان میدهد که ما در ساخت انیمیشن بسیار بیش از بودجه هستیم. در جدول زمانی به «قابها» بروید و نگاهی بیندازید:
این درخواستهای AnimationFrame (rAF) بیش از 200 میلیثانیه طول میکشد. این مرتبه بسیار طولانی است که نمی توان هر 16 میلی ثانیه یک فریم را علامت زد! باز کردن یکی از آن تماسهای طولانی rAF نشان میدهد که در داخل چه میگذرد: در این مورد، طرحبندی زیادی.
ویدیوی پل به جزئیات بیشتری در مورد علت خاص رله کردن (خواندن scrollTop
) و نحوه اجتناب از آن می پردازد. اما نکته اینجاست که میتوانید در پاسخ به تماس غوطهور شوید و بررسی کنید که چه چیزی اینقدر طول میکشد.
به زمان فریم 16 میلیثانیه توجه کنید. این فضای خالی در فریم ها فضایی است که شما باید کارهای بیشتری انجام دهید (یا اجازه دهید مرورگر کارهایی را که باید در پس زمینه انجام دهد) انجام دهد. این فضای خالی چیز خوبی است.
منبع دیگر جانک
بزرگترین دلیل مشکل هنگام اجرای انیمیشنهای مبتنی بر جاوا اسکریپت این است که چیزهای دیگر میتوانند مانع از برقراری تماس rAF شما شوند و حتی از اجرای آن جلوگیری کنند. حتی اگر پاسخ تماس rAF شما نازک باشد و تنها در چند میلی ثانیه اجرا شود، فعالیتهای دیگر (مانند پردازش یک XHR که به تازگی وارد شده است، اجرای کنترلکنندههای رویداد ورودی، یا اجرای بهروزرسانیهای برنامهریزیشده روی یک تایمر) ناگهان میآیند و برای هر دورهای اجرا میشوند. زمان بدون تسلیم شدن در دستگاه های تلفن همراه، گاهی اوقات پردازش این رویدادها می تواند صدها میلی ثانیه طول بکشد، در این مدت انیمیشن شما کاملا متوقف می شود. ما به آن انیمیشن ها می گوییم jank .
هیچ گلوله جادویی برای اجتناب از این موقعیت ها وجود ندارد، اما چند روش برتر معماری وجود دارد که می توانید خودتان را برای موفقیت آماده کنید:
- پردازش های زیادی را در کنترل کننده های ورودی انجام ندهید! انجام مقدار زیادی JS یا تلاش برای تنظیم مجدد کل صفحه در طول به عنوان مثال یک onscroll handler یکی از دلایل بسیار شایع jankiness وحشتناک است.
- تا آنجا که ممکن است پردازش (بخوانید: هر چیزی که زمان زیادی طول می کشد) را به پاسخ تماس rAF یا Web Workers خود فشار دهید.
- اگر کار را وارد تماس برگشتی rAF کردید، سعی کنید آن را تکه تکه کنید تا فقط کمی هر فریم را پردازش کنید یا آن را تا پایان یک انیمیشن مهم به تعویق بیندازید -- به این ترتیب می توانید به اجرای تماس های کوتاه rAF ادامه دهید و به آرامی متحرک سازی کنید. .
برای آموزش عالی در مورد نحوه فشار دادن پردازش به callbacks requestAnimationFrame به جای کنترل کننده ورودی، به مقاله Paul Lewis Leaner, Meaner, Faster Animations with requestAnimationFrame مراجعه کنید.
انیمیشن CSS
چه چیزی بهتر از JS سبک وزن در تماس های رویداد و rAF شما؟ بدون JS.
قبلاً گفتیم هیچ گلوله نقرهای برای جلوگیری از قطع تماسهای rAF شما وجود ندارد، اما میتوانید از انیمیشن CSS برای اجتناب از نیاز کامل به آنها استفاده کنید. مخصوصاً در Chrome for Android (و سایر مرورگرها روی ویژگیهای مشابه کار میکنند)، انیمیشنهای CSS دارای ویژگی بسیار مطلوبی هستند که مرورگر اغلب میتواند آنها را اجرا کند حتی اگر جاوا اسکریپت در حال اجرا باشد.
یک جمله ضمنی در بخش بالا در مورد jank وجود دارد: مرورگرها فقط می توانند یک کار را در یک زمان انجام دهند. این کاملاً درست نیست، اما این یک فرض کار خوب است: در هر زمان مرورگر میتواند JS را اجرا کند، طرحبندی یا نقاشی را انجام دهد، اما فقط یکی در یک زمان. این را می توان در نمای Timeline Dev Tools تأیید کرد. یکی از استثناهای این قانون، انیمیشن های CSS در کروم برای اندروید (و به زودی کروم دسکتاپ، البته هنوز نه) است.
در صورت امکان، استفاده از انیمیشن CSS هم برنامه شما را ساده میکند و هم به انیمیشنها اجازه میدهد حتی در زمانی که جاوا اسکریپت اجرا میشود، روان اجرا شوند.
// see http://paulirish.com/2011/requestanimationframe-for-smart-animating/ for info on rAF polyfills
rAF = window.requestAnimationFrame;
var degrees = 0;
function update(timestamp) {
document.querySelector('#foo').style.webkitTransform = "rotate(" + degrees + "deg)";
console.log('updated to degrees ' + degrees);
degrees = degrees + 1;
rAF(update);
}
rAF(update);
اگر روی دکمه کلیک کنید، جاوا اسکریپت به مدت 180 میلیثانیه اجرا میشود، که باعث jank میشود. اما اگر در عوض آن انیمیشن را با انیمیشن های CSS درایو کنیم، دیگر jank رخ نمی دهد.
(به یاد داشته باشید که در زمان نگارش این مقاله، انیمیشن CSS فقط در کروم برای اندروید بدون جک است، نه کروم دسکتاپ.)
/* tools like Modernizr (http://modernizr.com/) can help with CSS polyfills */
#foo {
+animation-duration: 3s;
+animation-timing-function: linear;
+animation-animation-iteration-count: infinite;
+animation-animation-name: rotate;
}
@+keyframes: rotate; {
from {
+transform: rotate(0deg);
}
to {
+transform: rotate(360deg);
}
}
برای اطلاعات بیشتر در مورد استفاده از انیمیشنهای CSS، به مقالاتی مانند این مقاله در MDN مراجعه کنید.
جمع بندی
کوتاه آن این است:
- هنگام انیمیشن سازی، تولید فریم برای هر تازه سازی صفحه اهمیت دارد. انیمیشن Vsync'd تأثیر مثبت زیادی بر احساس یک برنامه می گذارد.
- بهترین راه برای دریافت انیمیشن vsync'd در کروم و سایر مرورگرهای مدرن، استفاده از انیمیشن CSS است. هنگامی که به انعطاف پذیری بیشتری نسبت به انیمیشن CSS نیاز دارید، بهترین تکنیک، انیمیشن مبتنی بر requestAnimationFrame است.
- برای سالم و شاد نگه داشتن انیمیشنهای rAF، مطمئن شوید که سایر کنترلکنندههای رویداد مانع اجرای پاسخ تماس rAF شما نمیشوند و تماسهای rAF را کوتاه (کمتر از 15 میلیثانیه) نگه دارید.
در نهایت، انیمیشن vsync'd فقط برای انیمیشنهای ساده رابط کاربری اعمال نمیشود - بلکه برای انیمیشنهای Canvas2D، انیمیشنهای WebGL و حتی پیمایش در صفحات استاتیک نیز کاربرد دارد. در مقاله بعدی این مجموعه، عملکرد اسکرول را با در نظر گرفتن این مفاهیم بررسی خواهیم کرد.
متحرک سازی مبارک!