Як по маслу, або анимируем зі швидкістю 60 FPS на CSS 3

Зображення і текст належать їх авторам.
Анімація елементів в мобільних додатках — це просто. Правильна анімація теж може бути простою… якщо ви прислухаєтеся представленим у статті порад.
Сьогодні хто тільки не використовує CSS 3 анімацію в своїх проектах, тим не менш не лише всі, але мало хто може робити це правильно. Навіть описані так звані «кращі практики», але люди продовжують робити все по-своєму. Швидше за все тому, що просто не розуміють, чому все влаштовано саме так, а не інакше.

Спектр мобільних пристроїв тільки і робить, що розширюється, тому якщо ви не оптимізуєте свій код з урахуванням цього, такий підхід рано чи пізно дасть про себе знати у вигляді гальм програми, з якими стикатимуться ваші користувачі.
Запам'ятайте: не дивлячись на те, що у світі є кілька флагманських девайсів, постійно штовхають прогрес вперед, люди користуються своїм улюбленим антикваріатом, з яким їм хочеться розлучатися.
Коректне використання CSS 3 усуне частина проблем, тому ми хочемо допомогти вам у розумінні деяких речей.
Приборкуючи час
Що робить браузер в процесі візуалізації і управління всіма цими елементами на сторінці? Відповідь — ця проста шкала, названа CRP (Critical Rendering Path).
image

Щоб досягти плавності анімацій, нам необхідно сфокусуватися на зміну властивостей, які впливають на крок Composite.
Стилі
image

Браузер починає розраховувати стилі, щоб застосувати їх до елементів.
Каркас
image

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

Браузер формує елементи як окремі шари, застосовуючи до них такі властивості, як box-shadow, border-radius, color, background-color і так далі.
Загальна картина
image

Саме на даному етапі буде потрібно ваша магія, так як саме зараз браузер розмальовує все сформовані шари на екрані. Сучасні браузери відмінно анимируют чотири види властивостей, оперуючи трансформацією та напівпрозорість.
  1. Позиція. transform: translateX(n) translateY(n) translateZ(n);
  2. Масштабування. transform: scale(n);
  3. Поворот. transform: rotate(ndeg);
  4. Напівпрозорість. opacity: n;
Як досягти позначки в 60 FPS
Давайте почнемо з HTML і створимо просту структуру меню додатка всередині контейнера
.layout
.
<div class="layout">
<div class="app-menu"></div>
<div class="header"></div>
</div>

image

Неправильний шлях
.app-menu {
left: -300px;
transition: left 300ms linear;
}

.app-menu-open .app-menu {
left: 0px;
transition: left 300ms linear;
}

Помітили, які властивості ми змінюємо? Необхідно уникати використання трансформацій з властивостями left/top/right/bottom. Вони не дозволяють створювати плавну анімацію, тому що змушують браузер перезбирати шари кожен раз, а це подіє на всі дочірні елементи.
Результат приблизно такий:
image

Анімація гальмує. Ми перевірили тимчасову шкалу DevTools, щоб подивитися, що відбувається насправді, і ось результат:
image

Картинка ясно показує нестабільність FPS і, як наслідок, низьку продуктивність.
Використання трансформацій
app-menu {
-webkit-transform: translateX(-100%);
transform: translateX(-100%);
transition: transform 300ms linear;
}

.app-menu-open .app-menu {
-webkit-transform: none;
transform: none;
transition: transform 300ms linear;
}

На відміну від вищевказаних властивостей, трансформації застосовуються до вже отрисованным блокам, тобто на стадії Composite. У прикладі вище, ми як би говоримо браузеру, що перед початком анімації всі верстви вже будуть отрисованы і готові до маніпуляцій.
image

Тимчасова шкала показує, що FPS став більш рівним, тому і анімація буде виглядати трохи плавніше.
image

Анімація з використанням GPU
Все може бути ще краще. Для цього ми будемо використовувати графічний прискорювач.
.app-menu {
-webkit-transform: translateX(-100%);
transform: translateX(-100%);
transition: transform 300ms linear;
will-change: transform;
}

У той час як translateZ() або translate3d() все ще потрібні деякими браузерами, як якийсь хак, майбутнє за властивістю will-change. Воно вказує браузеру перемістити елементи в окремий шар так, щоб він потім не перевіряв весь каркас на предмет складання або відтворення.
image

Бачите, наскільки плавною стала анімація? Таймлайн це підтверджує:
image

FPS став ще більш стабільною, але все ж ще залишається один повільний відрізок анімації на початку. Виходячи із структури меню, в JS зазвичай пишуть приблизно таку обробку:
function toggleClassMenu() {
var layout = document.querySelector(".layout");

if(!layout.classList.contains("app-menu-open")) {
layout.classList.add("app-menu-open");
} else {
layout.classList.remove("app-menu-open");
}
}

var menu = document.querySelector(".menu-icon");
menu.addEventListener. ("click", toggleClassMenu, false);

Проблема в тому, що додаючи клас до контейнера
.layout
, ми змушуємо браузер перераховувати стилі ще раз, а це позначається на швидкості компонування і відтворення.
Як по маслу
Але що якби меню було розташоване за областю видимості? Зробивши це, ми задіяли б тільки той елемент, який дійсно необхідно анімувати, тобто наше меню. Для ясності — структура HTML:
<div class="menu">
<div class="app-menu"></div>
</div>
<div class="layout">
<div class="header"></div>
</div>

Тепер ми можемо контролювати стан меню дещо по-іншому. Ми будемо керувати анімацією через клас, який видаляється після того, як анімація закінчується, використовуючи подію transitionend.
function toggleClassMenu() {
myMenu.classList.add("menu--animatable");
myMenu.classList.add("menu--visible");
}

function onTransitionEnd() {
myMenu.classList.remove("menu--animatable");
}

var myMenu = document.querySelector(".menu"),
menu = document.querySelector(".menu-icon");

myMenu.addEventListener. ("transitionend", onTransitionEnd, false);
menu.addEventListener. ("click", toggleClassMenu, false);

Ну, а тепер усі разом. Вашій увазі повний приклад CSS 3, де все на своїх місцях:
.menu {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none;
z-index: 150;
}

.menu—visible {
pointer-events: auto;
}

.app-menu {
background-color: #fff;
color: #fff;
position: relative;
max-width: 400px;
width: 90%;
height: 100%;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
-webkit-transform: translateX(-103%);
transform: translateX(-103%);
display: flex;
flex-direction: column;
will-change: transform;
z-index: 160;
pointer-events: auto;
}

.menu--visible.app-menu {
-webkit-transform: none;
transform: none;
}

.menu--animatable.app-menu {
transition: all 130ms ease-in;
}

.menu--visible.menu--animatable.app-menu {
transition: all 330ms ease-out;
}

.menu:after {
content: ";
display: block;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.4);
opacity: 0;
will-change: opacity;
pointer-events: none;
transition: opacity 0.3 s cubic-bezier(0,0,0.3,1);
}

.menu.menu--visible:after{
opacity: 1;
pointer-events: auto;
}

image

А що показує тимчасова шкала?
image

Як-то так.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.