Оживлюємо UI: дорожня карта підходів до CSS animations
У житті кожного Front-end розробника настає момент, коли сайт уже наче й готовий, усі елементи на своїх місцях, responsive теж зроблено, але чогось не вистачає. Сайт не здається «живим», йому бракує інтерактивності. Уся динаміка закінчується на додаванні cursor: pointer до посилань / кнопок. Якісний UI сьогодні неможливий без інтерактиву та плавності, особливо якщо врахувати ту велику кількість інструментів, які доступні веброзробникам.
Мій комерційний досвід у розробці почався з роботи в компанії, яка спеціалізується на вебанімаціях. Там я вивчив і втілив у життя багато різних підходів до створення якісного UI. Від простих анімацій на CSS до малювання в Canvas і магічної технології WebGL (до речі, я був першим у компанії, хто почав використовувати WebGL в production, і це дало додаткові бонуси в роботі). Завдяки цікавим анімаціям мої сайти потрапляли на awwwards, що теж вважаю гарним досягненням для фронта.
Зараз працюю на позиції Software Engineer в Mate academy. І хоча основна спеціалізація компанії — це далеко не анімації, я продовжую «топити» за якісний і динамічний UI. Коли розробник робить перші кроки в анімаціях, дуже важливо мати певний рівень експертизи в галузі. Свого часу наставник допоміг мені з «фішками» і вказав на правильний roadmap у розвитку. Бажання допомогти людям отримувати задоволення від результату роботи стало основною мотивацією для написання цієї статті. Ми поговоримо про підходи до створення анімацій у вебі й розберемось, як зробити ваш UI «живим».
Ілюстрація Аліни Самолюк
Основи анімації
Передусім з’ясуємо, чому анімації такі важливі. Ми постійно взаємодіємо з різними предметами й отримуємо «зворотний зв’язок» на свої дії. Якщо кинути м’яч з певної висоти, його швидкість буде збільшуватись, поки він не приземлиться. Після зіткнення із землею він не зупиниться, а далі стрибатиме, однак висота «підстрибування» і швидкість буде постійно зменшуватись, доки не сягне стану спокою. Так працюють закони фізики. Ми як користувачі звикли до зворотного зв’язку, і такий підхід широко застосовується у цифрових технологіях. Згадайте, наприклад, звук затвора фотоапарата, який програється на телефоні, коли ви робите нове фото. Користувачам зручніше послуговуватися продуктом, коли вони бачать знайомі речі та отримують очікуваний фідбек.
Виділимо основні принципи анімацій:
Suitability. Анімації мають бути доречними. Здебільшого це фідбек на дію користувача чи фонова анімація, яка не відволікає. На сайті не має бути занадто «шумно».
Stability. Анімації мають доповнювати UI, а не ламати його. Нічого не має «стрибати» чи «розвалюватись», коли ви показуєте анімацію.
Unity. Варто звертати увагу на таймінги та функції плавності. Наприклад, таймінг анімації базових UI-елементів (кнопки, посилання тощо) має бути однаковим. Хорошим тоном буде виносити параметри таймінгу та easing в окремі змінні.
Usability. Недостатньо показати, що «в мене тут елементи плавно рухаються». Важливо, щоб цим було зручно користуватись. Занадто довга анімація, наприклад, буде радше заважати юзеру, ніж приносити задоволення.
Загалом про анімації в UI можна говорити як про гру барабанщика в групі. Його не помічають, поки він не зафейлиться.
CSS-анімації
Найпростіший спосіб оживити сайт — використовувати властивості transition або animation в CSS. Transition використовують для задання плавності анімації, але вона не анімує безпосередньо. Анімація запуститься, коли властивість зміниться. Наприклад, ми можемо задати transition: color 0.3s і при :hover змінювати властивість color. Властивість animation, навпаки, програє анімацію одразу. Переважно я використовую transition, оскільки анімацію потрібно запускати у відповідь на дії юзера. Animation можна застосувати для фонових анімацій.
UI без анімації має непривабливий вигляд. Ви можете мати крутий сайт, але якщо він «дерев’яний» і не реагує на дії користувача належним чином, то буде здаватися поламаним чи незакінченим. Наприклад, меню нижче прямо просить «додай transition»:
Додаймо трохи плавності:
Ми не змінювали поведінку під час наведення на посилання чи переходу від одного списку до іншого, тільки додали transition на відповідні властивості. Має набагато кращий вигляд, погодьтесь. Але чогось бракує. Під час переходу між списками всі елементи анімуються однаково, що не є «нативно». Згадайте, що відбувається з вагонами, коли поїзд починає рух, чи як рушають автомобілі після того, як загоряється зелений сигнал світлофора. Вони рухаються з певною затримкою.
Спробуємо використати цей підхід у нашій анімації. Вважатимемо елемент, на який клікнули, «ведучим», а інші будуть за ним слідувати. Технічно ми додаємо +1 до індексу (i) сусідніх елементів і застосовуємо transition-delay до кожного елемента i * 0.1s. Що далі елемент від «ведучого», то більшою буде затримка в анімації. Такий підхід називається stagger animation effect. На CSS Tricks є корисна стаття на цю тему.
Більше прикладів анімації дивіться на сайті Hotel spider.
У CSS transition-delay можна прописати залежно від позиції елемента в списку:
.selector:nth-child(1) { transition-delay: 0.1s; } .selector:nth-child(2) { transition-delay: 0.2s; } .selector:nth-child(3) { transition-delay: 0.3s; }
Якщо ви використовуєте препроцесори, можна застосувати вбудовані цикли та згенерувати потрібні стилі:
@for $i from 1 through 10 { &:nth-child(#{$i}) { transition-delay: #{$i/10}s; } }
Швидкість роботи сайту
Кажучи про website performance, перше, що спадає на думку, — це швидкість завантаження сторінки й бали в PageSpeed Insights. Крім кількісних показників, потрібно пам’ятати про те, що юзер відчуває, коли користується вашим сайтом. Коли виконується довга операція, наприклад надсилання даних форми на сервер чи перехід між сторінками, можна показати людині анімацію. Це перемкне увагу, і тоді здаватиметься, що дія виконалась набагато швидше.
Якщо ви пишете звичайний HTML без використання реактів / ангулярів, то для плавних переходів між сторінками рекомендую бібліотеку barba.js. Вона дає змогу зробити навігацію, як у Single Page Application.
Як це працює
Будь-яка анімація — це плавний перехід предмета з одного стану в інший. Щоб плавно змінити число з 0 до 1 за час t, ми можемо розділити процес на ітерації й на кожній збільшувати число на певний інтервал, доки воно не дорівнюватиме 1. Анімація з 0 до 1 — це основа, на базі якої можна побудувати будь-що.
Якщо навчитися анімувати число з 0 до 1, можна розрахувати стан будь-якого елемента, використавши формулу інтерполяції.
Це формула лінійної інтерполяції. У нашому прикладі під час зміни x від 0 до 1 y буде лінійно змінюватися від a до b.
60 FPS
У браузері анімації розраховуються зі швидкістю 60 FPS (frames per second). Чому 60, а не 30 чи 24?
24 FPS — стандарт для телебачення. Загалом для мозку достатньо
Якщо для відео різниця у швидкості програвання в ±5% непомітна, то для звуку така різниця критична. «24» стало стандартом, тому що це найменше число, яке ділиться на 2, 3, 4, 6 і 8 одночасно. Це суттєво спростило процес відеомонтажу. Під час програвання у 24 FPS можна помітити, як складні та швидкі сцени розмиваються. Такий ефект називається motion blur. Єдиний спосіб його позбутись — збільшувати frame rate.
30 FPS — в ігровій індустрії дуже важливо відмальовувати складні сцени без ефекту «мильної опери». Додаткові 6 кадрів допомогли розв’язати проблему без особливих навантажень на «залізо». Число 30 було обрано не випадково. В USA частота змінного струму — 60 Hz, і більшість телевізорів та моніторів можуть оновлювати картинку 60 разів за секунду. Фактично, якщо відмальовувати кожен другий кадр, отримаємо 30 FPS. Навіть у сучасних іграх можна вибрати альтернативу: 60 FPS і трохи гірша картинка чи 30 FPS і краща якість.
60 FPS — дає дуже якісний motion effect і допомагає позбутися розмиття картинки. Сьогодні це стандарт в анімаціях.
Є і більші рейти, наприклад 120 FPS. Але це вже окремі випадки, які вимагають спеціального обладнання (принаймні монітора, який може працювати на такій частоті). Потреба у високому frame rate виникає переважно в професійних геймерів, які можуть побачити різницю в динамічних іграх. Для рядового користувача достатньо 60 FPS.
Прочитати докладніше про frame rate можна тут. Порівняти картинку з різним frame rate — тут.
У браузері анімації розраховуються з частотою 60 FPS, тобто на підрахунок одного кадра в нас є ~16 мілісекунд. Небагато часу, погодьтесь. Тому дуже важливо не робити «дорогих» операцій, коли ми хочемо щось анімувати. Якщо браузер не встигає виконати розрахунки за 16 мілісекунд, наступний кадр пропускається, а не відкладається. Фактично браузер буде знижувати frame rate, що погано впливає на якість картинки.
FPS. Дебагінг
В Google Chrome перевірити поточний frame rate можна, використовуючи Chrome DevTools. Перший спосіб: відкрити DevTools і викликати Command Menu (Windows: Ctrl + Shift + P; mac: Cmd + Shift + P). Далі в пошуку набираємо FPS і вибираємо Show frames per second (FPS) meter.
Щоби приховати FPS meter, натискаємо відповідно Hide frames per second (FPS) meter.
Інший спосіб — відкрити вкладку Rendering і вибрати пункт Frame Rendering Stats:
У минулих версіях Google Chrome FPS meter показував реальне число поточного frame rate.
Після оновлення тут подана більш розгорнута статистика:
Відсоток вчасно відрендерених кадрів (16 мілісекунд на один кадр) показує, наскільки добре працює сайт. Що вище це число, то краще.
Я в повсякденній роботі використовую FPS extension від Юри Артюха. Це дає змогу швидко перевірити, чи все добре із сайтом. Функціонал Chrome здебільшого застосовую, коли є проблеми й потрібно віддебажити, де йде просадка FPS.
Подивитись, як відпрацьовують CSS-анімації, можна у вкладці animations. Тут і таймінги, і затримки, і криві easing. Можна все це редагувати й «перезапускати» анімацію зі скоригованими значеннями, що допомагає швидко обрати найкращий варіант.
Процес рендерингу в браузері
Коли ми читаємо документацію із transition, наприклад, то бачимо, що можна анімувати всі countable властивості. Тобто opacity можна анімувати з 0 до 1, а от display: none / display: block — не можна. Технічно перехід значень у display неможливо розбити на кадри. До речі, якщо потрібно приховати елемент і зробити його неклікабельним, спробуйте трюк з visibility: hidden і затримкою анімації. Докладніше про це написано в статті. Мені буде дуже приємно, якщо він допоможе вам пофіксити проблему з відкриттям / закриттям модалки чи бургер-меню.
Повернемось до чисел. В CSS багато countable властивостей, і під час анімації одних усе проходить плавно, а от інші «стрибають», коли намагаєшся їх анімувати.
Щоби зрозуміти, які властивості можна анімувати практично без затрат ресурсів, розберемося з рендерингом у браузері. Що відбувається, коли ми змінюємо стилі?
- Calculate styles — прораховуються стилі. Тут використовують CSS.
- Flow — будується структура сторінки. Розраховуються розміри елементів, відступи, і кожен елемент займає своє місце на сторінці.
- Paint — сторінка відмальовується. На цьому етапі рендеряться шрифти, картинки, застосовуються кольори тексту чи фону, тіні. Малюються всі візуальні елементи.
- Composite layout — елементи відмальовуються «пошарово». Це дуже добре видно, коли один елемент перекриває інший і потрібно вирахувати їхній порядок за допомогою z-index. Рендеринг з використаним transform чи opacity виносить елемент на окремий «шар», що допомагає зміщувати елементи візуально, не змінюючи їхнє фізичне розташування на макеті (transform), а також керувати прозорістю (opacity).
Докладніше про це в статті з оптимізації візуалізації від Google.
Наше завдання — зменшити кількість кроків після перерахунку стилів. Коли ми змінюємо стилі, браузер визначає, чи потрібно перерахувати позицію елементів (reflow), перемалювати кольори (repaint) чи тільки працювати з елементом у межах його «шару» (composite layout). Ідеальна анімація має викликати лише один крок — composite layout. Властивості, які дають змогу це зробити — transform та opacity. З їх використанням ваші анімації будуть дуже плавними та приємними.
Коли люди дізнаються, що не бажано анімувати нічого, що викликає reflow (розміри, відступи), одразу виникає купа питань типу «а як тоді змінити розміри чи відступи». У моїй кар’єрі було 1 чи 2 випадки, коли потрібно було анімувати щось, що викликає reflow. 99,9% загальних потреб покривають transform та opacity. Звісно ж, кольори ми теж іноді можемо анімувати, але це дорожче, ніж transform та opacity. І тут є трюк щодо оптимізації. Замість анімації, наприклад, властивості background-color з transparent на потрібну нам, можна додати псевдоелемент ::before з потрібним кольором фону та анімувати йому opacity.
/* bad */ .selector { background-color: transparent; transition: background-color 0.3s; } .selector:hover { background-color: red; } /* good */ .selector::before { background-color: red; opacity: 0; transition: opacity 0.3s; } .selector:hover::before { opacity: 1; }
Основне завдання — не викликати reflow. Подивитись, як зміна властивості впливає на процеси в браузерах, можна на сайті CSS Triggers.
Але не тільки зміна CSS здатна викликати reflow. Будьте обережні, коли використовуєте DOM-методи через JavaScript. Наприклад, розрахунок ширини вікна чи поточної позиції скролу теж робить це. Якщо ми будемо використовувати «дорогі» методи на кожен кадр анімації, отримати 60 FPS буде важко. Докладніше про методи, які негативно впливають на performance, читайте тут.
Поширеним підходом у цьому контексті є анімації під час скролу. Тоді контент сторінки схований, а коли юзер доскролює до потрібної секції, її контент проявляється. Раніше, щоб визначити, чи потрапляє елемент у поточний viewport, потрібно було вираховувати скрол сторінки, висоту вікна, відстань від початку документа до потрібного елемента. Тобто всі методи, які викликають reflow, викликались щоразу, коли юзер гортав сторінку.
Об’єм навантажень досить великий. В API сучасних браузерів з’явився Intersection Observer. Його пряме призначення — визначати, чи елемент міститься в межах viewport, і викликати переданий callback кожного разу, коли елемент входить в ділянку видимості чи виходить із неї. Оскільки це нативне API браузера, його performance кращий, ніж обчислення позиції елемента під час скролу вручну. На CSS Tricks є корисна стаття на цю тему.
Прискорення
Під час анімування елемента браузеру потрібно на кожен кадр розрахувати його координати на сторінці й те, як він взаємодіє з іншими елементами. Коли ми анімуємо «погані» властивості (ті, що викликають reflow), браузер змушений перераховувати елемент у межах всієї сторінки. Такий процес дуже дорогий, особливо для великих сторінок, де рендериться багато елементів.
Набагато дешевше винести елемент в окремий шар і перераховувати його координати вже там. Саме так і працює анімація transform чи opacity. Браузер створює окремо шар із потрібним елементом і шар зі сторінкою без цього елемента. Далі в межах свого шару все ще перераховуються координати, але лише одного елемента — того, який ми анімуємо. Потім шари відправляються на GPU і там рендеряться.
Сьогодні в браузерах можна подивитись, як сторінка ділиться на шари і як вони анімуються. В Chrome це можна глянути, відкривши вкладку layers.
Там можна навіть покрутити сторінку в 3D і подивитись на свій сайт під іншим кутом, що б це не означало.
Іноді під час анімації transform просідає FPS. У таких випадках можемо залучити GPU для обчислення стану елемента на кожен кадр анімації. Основна перевага GPU в тому, що, на відміну від CPU, який має кілька розумних ядер, відеокарта має тисячі ядер, які заточені під математичні операції й тільки під них.
Як ми вже пам’ятаємо, анімація — це проста математика, тому обчислювати стан елемента на GPU буде дуже доречним рішенням. Змусити сучасний браузер рахувати анімацію на відеокарті можна кількома способами:
- translateZ(0), translate3D(x, y, 0). Загалом у браузерах доступна анімація в 3D навіть у CSS, і вона рахується на GPU. Зараз нам не потрібно нічого анімувати в 3D, але ми можемо вказати, що елемент зміщується по осі Z на 0 пікселів. Браузер буде змушений обчислювати стан елемента на GPU.
- will-change: transform. Використовуючи властивість will-change в CSS, ми вказуємо браузеру на необхідність оптимізувати ресурси для анімації конкретного елемента.
Виникає питання, чому ми не можемо написати * {will-change: transform} чи * {transform: translateZ(0)} і забути про проблеми з оптимізацією? Річ у тім, що відеокарта — це окремий комп’ютер, і браузер має підготувати дані для відправки на GPU, а потім опрацювати відповідь. Тому, створивши багато шарів, які не використовуються, ми «стріляємо собі в ногу» і змушуємо браузер тратити ресурси на спілкування з GPU, що теж негативно позначається на FPS. Нижче приклад використання * {transform: translateZ(0)}
Як бачимо, на кожен елемент створився шар, і, відверто кажучи, браузер зависав щоразу, коли я намагався проскролити з відкритою вкладкою layers.
Тому не потрібно наосліп намагатись «оптимізувати». Можна зробити ще гірше. Гарний приклад розумної оптимізації — додавати через JavaScript will-change: transform перед виконанням складної анімації і видаляти після її завершення.
Корисні статті про оптимізацію анімацій:
- CSS GPU Animation: Doing It Right — Smashing Magazine
- Let’s Play With Hardware-Accelerated CSS — Smashing Magazine
- will-change — CSS | MDN
- animation will-change
3D в CSS
Ми вже згадували про додавання translateZ(0) чи translate3d(x, y, 0) як хак для оптимізації анімацій. Але при зміні значень з 0 на будь-яке число нічого не відбувається. Щоб увімкнути 3D-анімації в браузері, потрібно на батьківський елемент додати властивість perspective, тоді на елементи всередині такого контейнера можна буде застосовувати 3D-анімації та побачити ефект.
.box { perspective: 500px; &:hover { span { transform: translateZ(200px) } } }
Приклад на codepen — click.
В production я використовував цей підхід для on-load анімації.
Для тих, хто хоче розібратися в 3D-анімаціях у CSS докладніше, залишаю лінк на корисний гайд. Загалом основна ідея в тому, щоб мати властивість perspective на батьківському елементі. Далі можна експериментувати.
Де брати натхнення
Щоб не стояти на місці та розвиватися в напрямі motion UI development (о як сказав, аж самому сподобалось), потрібно постійно прокачувати свій vision level. Тобто розширювати кругозір і мати розуміння, на що здатні браузери сьогодні. Не обов’язково вміти робити всі анімації, достатньо знати, що це можливо зробити. Далі вже справа техніки. Надихатись новими ідеями можна через відвідування профільних ресурсів. Виділю декілька з них:
- awwwards — тут щодня з’являються десятки нових сайтів та обираються найкращі. Неймовірна кількість ідей і реалізацій. Якщо зробите reverse-engineering анімації, яка вам сподобалась, набагато прокачаєте свої скіли.
- codrops — підбірка статей про креативні рішення для ваших сайтів. Є приклади коду й гайди, як зробити ту чи іншу анімацію. Дуже рекомендую як для загального розвитку, так і для втілення рішень у вашій роботі.
- Канал Юри Артюха на YouTube — тут ви зможете знайти відео на різноманітні теми фронтенду та анімацій. Від «верстки сторінки за годину» до розбору складних анімацій на WebGL. Усе це з поясненнями та прикладами коду.
Я рекомендую проводити на таких ресурсах щонайменше
Резюме
На завершення хотілося б дати кілька рекомендацій, які допоможуть вам стати кращими девелоперами:
- Копайте глибоко. Намагайтесь розібратись, чому та чи інша річ працює саме так, а не інакше. Знання маленьких деталей і вміння ними користуватись суттєво виділяє вас серед інших розробників. Це стосується не тільки анімацій, а й розробки загалом.
- Використовуйте знання на практиці. Сподобався приклад анімації? Розберіться, як це працює, і напишіть свій. Вийшло нове відео з лайвкодингом? Повторіть. Знання краще засвоюються, коли використовуєш їх на практиці, а не відкладаєш лінк на матеріал в bookmarks у браузері.
- Прокачуйте свій vision level. Розглядайте профільні ресурси, дивіться, як зроблені інші сайти.Одне слово, розширюйте кругозір.
- Не оптимізовуйте наосліп. Дивіться метрики й навчіться дебажити проблему.
- Поглиблюйте знання в математиці. Не обов’язково отримувати PhD, але розуміння базової геометрії / тригонометрії суттєво спростить роботу з анімаціями. Наприклад, у мене було завдання зробити пагінацію в слайдері, де точки розподіляються по колу й коло замальовується залежно від індексу поточного слайду:
Водночас кількість точок може змінюватись відповідно до кількості слайдів, і їхні координати треба динамічно рахувати. Не знаючи базових формул на кшталт знаходження координати точки на колі, така задача стає майже неможливою для виконання.
Все про українське ІТ в телеграмі — підписуйтеся на канал DOU
10 коментарів
Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.