Статьи

Достигните 60 FPS мобильных анимаций с CSS3

Эта статья была первоначально опубликована на OutSystems . Спасибо за поддержку партнеров, которые делают возможным использование SitePoint.

Анимировать элементы в вашем мобильном приложении очень просто. А также правильно анимировать элементы в ваших мобильных приложениях … если вы будете следовать нашим советам здесь.

Хотя в наши дни все используют анимацию CSS3 в мобильных устройствах, многие делают это неправильно. Разработчики часто игнорируют лучшие практики. Это происходит потому, что люди не понимают причин, по которым эти практики существуют, и почему они так энергично поддерживаются.

Спектр технических характеристик устройства широк. Так что, если вы не оптимизируете свой код, вы получите опыт ниже среднего.

Помните: некоторые высококлассные флагманские устройства выходят за рамки возможного, но в большинстве стран мира используются устройства, которые по сравнению с этими техническими монстрами выглядят как счеты с ЖК-экраном.

Мы хотим помочь вам правильно использовать возможности CSS3. Чтобы сделать это, нам нужно сначала понять несколько вещей.
T

Понять временную шкалу

Что делает браузер при рендеринге и игре с элементами? Эта временная шкала называется Critical Rendering Path:

Критический путь рендеринга

Источник изображения: www.csstriggers.com

Чтобы добиться плавной анимации, нам нужно сосредоточиться на изменении свойств, которые влияют на шаг Composite , вместо того, чтобы добавлять это напряжение к предыдущим слоям.

1. Стили

Стили

Браузер начинает вычислять стили, применяемые к элементам — пересчитывать стиль .

2. Макет

раскладка

В следующем слое браузер генерирует форму и положение каждого из этих элементов — Layout . Именно здесь браузер устанавливает свойства страницы, такие как width и height , а также, например, ее margin или left/top/right/bottom .

3. Краска

Покрасить

Браузер заполняет пиксели для каждого элемента слоями. Это относится к этим свойствам: box-shadow , border-radius , color , background-color и другие.

4. Композитный

Здесь вы хотите выполнить анимацию, как это происходит, когда браузер рисует все слои на экране.

композитный

Современные браузеры могут очень хорошо анимировать четыре атрибута стиля, используя свойства transform и opacity .

  • Положение — преобразование: translateX (n) translateY (n) translateZ (n);
  • Масштаб — преобразование: масштаб (n);
  • Вращение — преобразование: вращение (ndeg);
  • Непрозрачность — непрозрачность: n;

Как достичь 60 кадров в секунду

Имея это в виду, пришло время засучить рукава и приступить к работе.

Давайте начнем с HTML. Мы собираемся создать очень простую структуру и поместить наше приложение в меню класса макета.

<div class="layout"> <div class="app-menu"></div> <div class="header"> <div class="menu-icon"></div> </div> </div>
<div class="layout"> <div class="app-menu"></div> <div class="header"> <div class="menu-icon"></div> </div> </div> 

приложение-меню внутри класса макета

Неправильный путь

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

Видите свойства, которые мы изменили? Вы должны избегать использования переходов со свойствами left/top/right/bottom . Они не создают плавные анимации, потому что они заставляют браузер каждый раз выполнять макет, который затрагивает все дочерние элементы.

Результат примерно такой:

Избегайте переходов

Эта анимация не слишком плавная. Мы проверили временную шкалу DevTools, чтобы увидеть, что происходит под капотом, и это было результатом:

Хронология DevTools

Зеленые области представляют время, затраченное на рендеринг анимации.

Эти данные представляют нерегулярную частоту кадров и низкую производительность.

«Зеленая полоса обозначает FPS. Высокая полоса указывает, что анимация рендерится со скоростью 60 кадров в секунду. Низкая полоса указывает на суб-60 FPS. Итак, в идеале, вы хотите, чтобы зеленая полоса была постоянно высокой по всей временной шкале. Эти красные столбцы также указывают на рывок, поэтому, в качестве альтернативы, еще один способ оценить ваш прогресс — это устранить эти красные столбцы ».
Спасибо, Кейси Баски !

Использование Transform

.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; }
.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; } 

Свойства transform влияют на шаг Composite , а не на Layout . Здесь мы сообщаем браузеру, что наши слои стабильны до начала анимации, поэтому при рендеринге анимации мы испытываем меньше сбоев.

Использование Transform

Это именно то, что Timeline отражает:

Временная шкала с использованием преобразования

Результаты начинают улучшаться, частота кадров стабилизируется и, следовательно, анимация работает плавнее.

Запуск анимации в GPU

Тогда давай займемся этим. Чтобы все прошло гладко, мы будем использовать графический процессор для рендеринга анимации.

.app-menu { -webkit-transform: translateX(-100%); transform: translateX(-100%); transition: transform 300ms linear; will-change: transform; }
.app-menu { -webkit-transform: translateX(-100%); transform: translateX(-100%); transition: transform 300ms linear; will-change: transform; } 

Хотя некоторые браузеры все еще нуждаются в translateZ() или translate3d() качестве запасных вариантов, свойство will-change является будущим. Это свойство переводит элементы в другой слой, поэтому браузеру не нужно учитывать макет или рендеринг.

Анимации в ГПУ

Видишь, как это гладко? Сроки подтверждают, что:

Хронология анимации

Частота кадров анимации более постоянна, и анимация отображается быстрее. Но в начале все еще работает длинная рама: немного узкого места в начале.

Помните структуру HTML, которую мы создали в начале? Давайте посмотрим, как мы контролировали div app-menu в JavaScript:

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 oppMenu = document.querySelector(".menu-icon"); oppMenu.addEventListener("click", toggleClassMenu, false);
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 oppMenu = document.querySelector(".menu-icon"); oppMenu.addEventListener("click", toggleClassMenu, false); 

Ага! Мы вызвали проблему, добавив класс в layout div. Это заставило браузер восстановить наше дерево стилей — и это сказалось на производительности рендеринга.

60 FPS, гладкий как масляный раствор

Что если мы создадим меню за пределами области просмотра? Наличие меню в изолированной области гарантирует, что мы воздействуем только на элемент, который мы хотим анимировать.

Итак, мы предлагаем следующую структуру HTML:

<div class="menu"> <div class="app-menu"></div> </div> <div class="layout"> <div class="header"> <div class="menu-icon"></div> </div> </div>
<div class="menu"> <div class="app-menu"></div> </div> <div class="layout"> <div class="header"> <div class="menu-icon"></div> </div> </div> 

И теперь мы должны контролировать состояние меню немного по-другому. Мы собираемся манипулировать анимацией в классе, который мы удаляем, когда анимация заканчивается, используя функцию transitionend в JavaScript.

function toggleClassMenu() { myMenu.classList.add("menu--animatable"); if(!myMenu.classList.contains("menu--visible")) { myMenu.classList.add("menu--visible"); } else { myMenu.classList.remove("menu--visible"); } } function OnTransitionEnd() { myMenu.classList.remove("menu--animatable"); } var myMenu = document.querySelector(".menu"); var oppMenu = document.querySelector(".menu-icon"); myMenu.addEventListener("transitionend", OnTransitionEnd, false); oppMenu.addEventListener("click", toggleClassMenu, false); myMenu.addEventListener("click", toggleClassMenu, false);
function toggleClassMenu() { myMenu.classList.add("menu--animatable"); if(!myMenu.classList.contains("menu--visible")) { myMenu.classList.add("menu--visible"); } else { myMenu.classList.remove("menu--visible"); } } function OnTransitionEnd() { myMenu.classList.remove("menu--animatable"); } var myMenu = document.querySelector(".menu"); var oppMenu = document.querySelector(".menu-icon"); myMenu.addEventListener("transitionend", OnTransitionEnd, false); oppMenu.addEventListener("click", toggleClassMenu, false); myMenu.addEventListener("click", toggleClassMenu, false); 

Давайте сложим все вместе и проверим результаты. Вот полный полностью включенный пример CSS3 со всем в нужном месте:

.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.3s cubic-bezier(0,0,0.3,1); } .menu--visible.menu:after { opacity: 1; pointer-events: auto; }
.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.3s cubic-bezier(0,0,0.3,1); } .menu--visible.menu:after { opacity: 1; pointer-events: auto; } 

Полностью включенный пример CSS3

А что Timeline показывает нам?

Временная шкала примера CSS3

Зеленые полоски на сутки, детка. Хотите увидеть живой пример? Нажмите здесь