Эта статья была первоначально опубликована на 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, чтобы увидеть, что происходит под капотом, и это было результатом:
Зеленые области представляют время, затраченное на рендеринг анимации.
Эти данные представляют нерегулярную частоту кадров и низкую производительность.
«Зеленая полоса обозначает 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 . Здесь мы сообщаем браузеру, что наши слои стабильны до начала анимации, поэтому при рендеринге анимации мы испытываем меньше сбоев.
Это именно то, что 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; }
А что Timeline показывает нам?
Зеленые полоски на сутки, детка. Хотите увидеть живой пример? Нажмите здесь