Розбираємося з анімацією в jQuery

анімацією
Привіт. Сьогоднішній топік я хотів написати про те, що механізм анімації в jQuery не ефективний, створює купу таймерів, кожен з яких працює окремо, що призводить до надмірно частого перемальовування контенту і гальмує браузер, і хотів описати деякі прийоми написання «правильної анімації». У ході підготовки прикладів я зрозумів, що я помилявся. Механізм анімації jQuery дійсно не ефективний, створює купу проблем, але причини цих проблем зовсім не у створенні великої кількості таймерів, а в дечому зовсім іншому, і, здається, я досяг чудових результатів усунення цих проблем.

Отже, мова піде про розсинхронізацію руху анімованих об'єктів, навіть якщо всі вони були анімовані одним викликом animate. Давайте розглянемо невеликий приклад одночасної анімації великої кількості об'єктів:

div class = "container" > div class = "animate" > div > div > script type ="text/javascript" > $( function () for ( var i = 0; i '.animate' ).clone().appendTo( '.container' ); > $( '.animate' ). animate( left: 800 >, 500); >); script > * Цей source code був висвітлений з Source Code Highlighter .

Теоретично всі ці об'єкти повинні рухатися синхронно. Насправді ми отримуємо таку картину:

Зрозуміло, що тест синтетичний, у реальній сторінці слід би рухати батька всіх цих об'єктів, але результат в наявності, анімація навіть у межах одного виклику animate стартує і закінчується в різний час. Тут було б логічним припустити, що для кожного об'єкта створюється окремий таймер, який його обслуговує. Але як я вже говорив вище, це припущення не є вірним. Порившись трохи у вихідниках, мені стало зрозуміло, що створюється толкоодин таймер на анімацію елементів всередині одного виклику animate. Більше того, для будь-якої анімації на сторінці, зроблену за допомогою jQuery, використовується лише один таймер, який видаляється після завершення останньої анімації на сторінці і створюється заново за нової необхідності щось анімувати.

Проте причина розсинхронізації близька до множинності таймерів, вона полягає в тому, що для кожного елемента час старту анімації вважається незалежно. Якщо елементів багато, між моментами підрахунку часу першого і останнього елемента, може пройти досить багато часу. Причому цей час буде тим більше, чим повільніше javascript-движок браузера, що ми і бачимо на скріншоті - значення розсинхронізації у опери 110 пікселів (скриншот в масштабі 1:2), у хрому 74 пікселів, а в іншого, дуже популярного, розсинхронізація браузера більше ширини тестового забігу, восьмиста пікселів.

Крім розбіжності анімації різних об'єктів, можливе розбіжність в анімації різних властивостей однієї й тієї ж об'єкта з тих-таки причин. Це може зіпсувати, наприклад, синхронну зміну паддингу та ширини, які в сумі повинні залишатися рівними. У реальній ситуації край об'єкту стрибатиме.

А тепер найголовніше. Враховуючи те, що таймер один, підправити час запуску анімації не є складним і за пару хвилин були внесені необхідні виправлення, що вирішують ці проблеми. Ці виправлення полягають у тому, що час старту анімації для всіх елементів вважається один раз при вході в функцію animate. Я навіть пішов далі, вніс в опції функції animate додатковий параметр startTime, який може приймати значення часу початку анімації. Це дозволить синхронізувати будь-яку кількість викликів функції animate між собою.

Крім синхронізації, можна використовувати початкове значення часу ще для кількох цікавих штук: 1) Задати час старту анімації в минулому і починати анімацію з середини. Зручно тим, що не доведеться виконувати окрему функцію ізінгу (easing) для половинки анімації. 2) Задати час старту в майбутньому, давши повільним движкам браузерів (привіт, IE!) прожувати всі об'єкти. Тут, щоправда, необхідна функція ізінгу, що призводить негативні зміщення часу до нульової координати. стандартні linear і swing не мають таких властивостей. 3) Робити анімацію у кілька проходів або нескінченну анімацію, задаючи час старту анімації у майбутньому. І тому потрібна буде функція изинга, здатна чітко обробити негативне усунення часу. У силу того, що стандартна функція ізінгу swing побудована на Math.cos, вона задовольняє цю вимогу.

Примітка, для тих хто не в курсі — функції ізінгу (easing, українською, напевно, це пом'якшення), це функції, що набувають значення від 0 до 1, де нуль — початковий час, 1 — кінцевий час анімації, і повертають значення також від 0 до 1, де 0 - початкове значення властивості, що змінюється, а 1 - кінцеве. У загальному випадку значення властивостей можуть лежати за межами 0 і 1, ну і з моїм патчем час теж може бути меншим за нуль.

Зміни, які я вніс у код

Час поточного кроку обчислювався функції jQuery.fx.step так: var t = now(); Я додав функції ще один параметр time та змінив визначення часу так: var t = time now();

До функції jQuery.fx.custom довелося додати ще один параметр startTime і обчислювати час старту анімації схожим чином з попередньою функцією: this .startTime = startTime now();

Крім того, всередині того самого єдиного setInterval було доданоотримання часу: var time = now (); і наступна передача цього часу до функцій з масиву, jQuery.timers[], який містить функції jQuery.fx.step.

У функції animate об'єкт optall був доповнений параметром startTime: optall = jQuery.extend(, optall)

Скажу чесно, виправлення особисто мені дуже потрібне, якщо я не знайду жодних проблем, я використовуватиму його як мінімум у своїх проектах. Але мене турбує те, що виправлення таке просте, але розробники jQuery не зробили його, хоча зробили набагато більшу підготовчу роботу з перенесення всієї анімації на один таймер. Можливо, в цьому є логіка і я чогось не бачу? Хто як вважає, чи варто намагатися проштовхнути мої зміни у фреймворк?

PS А ще я вчора зіткнувся з неробочою прозорістю в Опері 9.2 та останніх версіях jQuery. Опис проблеми та тимчасове рішення.

Хардкорна конфа за С++. Ми запрошуємо лише профі.