Природна анімація в інтерфейсах

Анімація в інтерфейсі робить наочною зміну стану. Наприклад, при невдалому надсиланні довга форма прокручується до неправильно заповненого поля. Або фотографія, що збільшується за натисканням, розсуває навколишні елементи.

Без анімації складніше сприймати різкі та раптові зміни. Водночас анімація має бути короткою та ненав'язливою, щоб не заважати користувачеві.

Анімація здається природною, коли повторює звичний рух предметів навколишнього світу. Під катом я розповім, як робив анімацію на основі фізичних законів. Дивіться готовий результат на демо-сторінці (там один блок слідує за іншим під час руху миші).

Згадуємо фізику

Переміщення об'єктів описується зміною координат x протягом t. Якщо ви спробуєте підібрати функцію x(t) «на око», ви витратите багато часу, домагаючись плавного та природного руху. Що вибрати? Гіперболу? Параболу? Куди її перемістити? Як повернути?

За прикладами руху найкраще звернутися до предметів навколишнього світу. Математичний закон їхнього руху диктується фізикою. Натисніть брусок, що лежить на столі. Він проходить певну відстань, сповільнюючись під впливом сили тертя. У хорошому наближенні сила сухого тертя ковзання стала, і залежність x(t) виявляється параболою. Таке уповільнення можна використовувати, якщо в початковий момент об'єкт анімації рухався.

Мал. 1. Гальмування сухим тертям по параболі

Сила в'язкого тертя пропорційна швидкості руху тіла. У такому разі тіло рухатиметься до точки зупинки експонентом за нескінченно великий час. Якщо експоненту спотворити, щоб обмежити час руху, така анімація здаватиметься неприродною. Через труднощі із зупинкою в розумний час неслід використовувати модель в'язкого тертя, тільки якщо симуляція в'язкого тертя не є метою.

Мал. 2. Гальмування експонентом у в'язкому середовищі

Відхилений від положення рівноваги маятник (або вантаж на пружині) плавно набирає швидкість, проходить положення рівноваги і плавно гальмує. Потім рух повторюється у зворотний бік, і так до нескінченності (якщо тертя немає). Графік такого руху – синусоїда. Періодичний повтор нам не дуже цікавий, а ось рух маятника між крайніми точками виходить плавним і природним.

Мал. 3. Рух маятника по синусоїді між крайніми точками

У JS-бібліотеках та CSS є заготівлі easing-функцій для створення спеціальних ефектів. Майже всі заготовки слід використовувати у спеціальних випадках з обережністю. Тільки синусоїда більш-менш універсальна.

По-перше, синусоїдальна траєкторія переводить тіло з одного положення в інше. По-друге, тривалість такого руху дорівнює половині періоду. Рух обмежений у часі. Тривалість не залежить від зовнішніх обставин та початкових умов. Вона залежить лише від властивостей самої системи та визначається співвідношенням жорсткості та інерційності.

Зазвичай я вибираю тривалість анімації по синусоїді 200 мілісекунд. Така тривалість у кілька разів більша за час реакції людини. Анімація добре помітна, але не встигає дратувати.

Давайте навчимося проводити синусоїдальну траєкторію за початковими умовами, часом руху та точкою зупинки.

Як провести синусоїду через дві точки

Нехай тіло спочиває в початковий і кінцевий момент часу. Тоді дотичні до графіка в точках t1 і t2 горизонтальні, а сам графік це напівперіод синусоїди.

Мал. 4. Графік рухуміж двома положеннями спокою

Рівняння, що описує напівперіод синусоїди, легко підібрати:

Після закінчення однієї анімації ми можемо починати іншу знову за цією формулою. Але що робити, якщо нова анімація має початися, поки не закінчилася стара? Щоб забезпечити плавність руху, ми зупиняємо поточну анімацію (синя лінія) та починаємо нову анімацію (червона лінія) з ненульовою початковою швидкістю:

Мал. 5. Графік руху з ненульовою початковою швидкістю

Без математичних обчислень не вдасться написати формулу, що відповідає червоній лінії. Давайте зробимо ці обчислення.

Сімейство всіх можливих синусоїд описується рівнянням

з чотирма невідомими параметрами A, B, C і 0"/>. Я зрушив початок звіту часу в точку t2, щоб відразу позбавитися другого складового. Дійсно, похідна повинна бути нульовою, тому що дотична в точці t2 горизонтальна. Це можливо, коли B = 0.

Оскільки , то підставляючи (1), отримуємо . Звідси виключаємо C:

Продиференціюємо, щоб знайти швидкість

Нам відоме положення x1 та швидкість v у початковий момент часу:

З цієї системи рівнянь потрібно знайти A та . Час вводити нову змінну замість . Її сенс - різниця фаз синусоїди в початковій та кінцевій точці. Наприклад, для графіка на рис. 4 тому, що на проміжку укладається напівперіод синусоїди. На рис. 5, тому що менше половини періоду.

Після підстановки та невеликих перетворень приходимо до системи

Розділимо почленно перше рівняння на друге:

Параметр у правій частині відомий заздалегідь. Він визначає необхідний характер руху. Якщо , то початкова швидкість мала, тіло спочатку має прискоритись. Якщо початкова швидкість велика, тіло повинносповільнюватися.

Тригонометричні функції у лівій частині зводяться до тангенсу половинного кута. У результаті ми нелінійне рівняння щодо k:

Проаналізувати його рішення можна на графіку. Намалюємо графік лівої та правої частини при деяких значеннях параметра:

Мал. 6. Графічне рішення рівняння (2)

Обговоримо рішення.

Розглянемо точку A. Це рішення існує при 1/2"/> і відповідає зображеному на малюнку 5: . Як очікувалося, . У межі нульової швидкості , червона пряма збігається з віссю ординат, точка A піде по тангенсоіді в нескінченність. .Поки все йде правильно.

Точка C відповідає значенню. Таке трапляється, коли тіло в перший час рухається вперед, а треба рухатися назад. Тепер. Рух описується фрагментом синусоїди, більшим за напівперіод, але меншим, ніж період: . Тіло гальмує, зупиняється, рухається назад і зупиняється у потрібному місці.

З графіка видно, що точка B потрапляє в діапазон . Тіло пройде по синусоїді більше, ніж повний період коливань: . Причина такого дивного рішення в тому, що точка зупинки знаходиться надто близько в порівнянні з характерною відстанню. Тому провести синусоїду без додаткової зупинки та повернення не вдасться.

Наближене рішення

Ми вирішили математичне завдання проведення синусоїди, але життя від цього простіше не стало. По-перше, щоб визначити параметри синусоїди, треба розв'язати нелінійне рівняння (2). Привіт ітераційні методи! По-друге, рівняння має безліч рішень, а необхідне рішення не завжди існує.

Ці труднощі виникли від того, що ми зафіксували тривалість анімації рівно 200 мілісекунд. Однак нічого страшного не станеться,якщо анімація триватиме, скажімо, 180 мілісекунд. Або навіть 250 мілісекунд. Нам важливіше зупинка у заданому місці, а точною тривалістю анімації ми пожертвуємо для спрощення розрахунків.

Послабивши вимоги на тривалість анімації, ми зробимо такий трюк. Припустимо, що ми маємо наближене рішення нелінійного рівняння (2). Воно є точним розв'язком рівняння з іншим параметром

Йому відповідає інший час закінчення анімації:

Тепер невідомі параметри траєкторії A та елементарно виражаються через і .

Я підібрав відповідне для наших цілей наближення до рівняння (2):

Синя суцільна лінія відповідає точному рівнянню (2), а червона пунктирна його наближенню:

Мал. 7. Порівняння точного співвідношення (2) та його наближення

А ще у разі пропоную взяти трохи більше, ніж 1/2, і скоротити час анімації, щоб уникнути відскоку та повернення.

Застосування

Код та сферичний приклад використання є на демо-сторінці. Поведіть мишею і подивіться, як чорний блок слідує за помаранчевим.

Ідею та початкову реалізацію знайшов на демо-сторінці js-парсеру markdown-it. У їхньому варіанті анімація вийшла рваною і підгальмовує. Тому є кілька причин:

  1. Для анімації застосовується лінійна функція $(. ).stop(true).animate(, 100, 'linear') . Замість гладкого графіка виходить ламана.
  2. Анімація через jQuery().stop().animate() гальмує порівняно з requestAnimationFrame() .
  3. Щоб уникнути падіння продуктивності, «ковтаються» події onscroll, наступні частіше ніж 50 мілісекунд. У моєму варіанті такої проблеми немає. Послідовні події onscroll коригують положення точки зупинки і не уповільнюють анімацію.

Щоб досягтиВажливою для продукту якісної анімації, я пропрацював метод обчислення на основі фізичних рівнянь, і реалізував його через спеціальний браузерний метод requestAnimationFrame() . Метод добре працює при будь-якій прокручуванні: клавішами PageUp/PageDown, через переміщення смуг прокручування, коліщатко миші, тачпад, тачскрин.