Производительность CSS и JavaScript анимации

Анимация является критичным инструментов для улучшения пользовательнского опыта во многих приложениях. Существует много путей создания анимации в web, например, основанные на CSS-свойствах transitions/animations или на JavaScript  (using requestAnimationFrame()). В этой статье мы проанализируем производительность CSS и JavaScript анимаций и сравним их.

CSS transition и animation

Оба этих свойства могут использоваться для создания анимации. Каждое из них имеет своё специфичное назначение:

  • CSS transitions предоставляет простой способ создать анимацию, которая происходит при переходе от текущего состояния к конечному, например, переход от обычной кнопки к кнопке в состоянии hover.  Даже если элемент в середине перехода от одного стиля к другому, новый эффект transition стартует немедленно, вместо того, чтобы дожидаться, пока запущенный ранее эффект завершится. Подробнее здесь:  Использование CSS transitions.
  • CSS animations, с другой стороны, позволяет разработчикам создавать анимацию, основанную на ключевых кадрах (keyframes), которые указывают этапы, которые должна пройти анимация от начального до финального состояния. CSS animation состоит из двух компонент: описание свойства, которое указывает на анимацию, а так же набор ключевых кадров, которые указывают начальное, финальное и промежуточные состояния элемента. Подробнее здесь: Использование CSS animations.

Если говорить о производительности - между этими двумя подходами нет разницы. Оба подхода основаны на одном и том же механизме, которые описаны далее.

requestAnimationFrame

API requestAnimationFrame() предоставляет эффективный способ создания анимаций в JavaScript. Функция (callback), которую вы передаете в этот метод, будет вызываться перед каждой следующей отрисовкой нового фрейма. Главное отличие от setTimeout()/setInterval() в том, что здесь вам не нужно указывать время, через которое функция запустится. requestAnimationFrame() работает гораздо эффективнее, учитывая частоту кадров и производительность системы. Разработчики могут создавать  анимацию, просто изменяя стили элемента каждый раз, когда происходит подготовка нового кадра (или когда обновляется полотно Canvas или в других случаях).

Заметка: Подобно CSS transition и animation, requestAnimationFrame() приостанавливает работу, когда текущий таб переводится в фоновый режим (например, при смене фокуса)

Для подробностей ознакомьтесь с анимирование с JavaScript: от setinterval до requestAnimationFrame.

Сравнение производительности:
transitions и requestAnimationFrame

По факту, в большинстве случаев, производительность анимаций CSS практически идентична анимациям на JavaScript. По крайней мере в Firefox. Авторы некоторых JavaScript библиотек для анимации, например GSAP или Velocity.JS, даже берутся утверждать, что их решения могут работать быстрее, чем аналогичные решения на CSS. Такое возможно, потому что CSS transitions/animations просто заново вычисляют стили элементов в основном потоке процессора сразу перед тем, как срабатывает событие repaint, что примерно то же самое, что вычислять стили заново с помощью requestAnimationFrame(). Если обе анимации выполняются в одном потоке, то разницы в производительности не будет.

В следующей секции мы пройдемся по тестам производительности, используя Firefox, чтобы увидеть, какие методы анимации работают эффективнее.

Включение измерения частоты кадров FPS

Для начала нам нужно включить инструменты измерения частоты кадров (FPS Tools), чтобы иметь возможность видеть текущую частоту кадров

  1. В поле ввода URL наберите about:config; Нажмите на кнопку I’ll be careful, I promise!, чтобы войти на страницу конфигурации.

     
  2. В поле поиска введите layers.acceleration.draw-fps.
  3. Нажмите два раза на ячейку, чтобы присвоить значение true. Теперь вы видите три розовых блока в верхнем левом углу окна. Первый блок указывает FPS.

Запуск теста

Для начала, в нашем тесте мы будем анимаировать 1000 элементов <div>  с помощью CSS.

Нажав на кнопку, вы можете переключить метод анимации на  requestAnimationFrame().

Попробуйте запустить оба метода и сравнить FPS. Скорее всего, вы увидите, что частота кадров отличается - анимации с CSS заметно быстрее. В следующей главе мы разберём - почему.

Анимация вне основного потока процесса

Браузерный JavaScript является строго однопоточным языком, то есть он не может одновременно работать над двумя задачами. В этом кроется проблема анимации с помощью JavaScript. Выполняя такую анимацию, вы занимаете процессор, который мог бы в это время заниматься другими функциями. В противоположность этому, CSS анимации могут быть выделены в отдельный поток, то есть при выполнении таких анимаций браузер не блокирует выполнение других процессов. 

Для того, чтобы выделить анимацию CSS в отдельный процесс, нам нужно убедиться, что изменяемые свойства не запускают этапы reflow/repaint (подробнее здесь: CSS triggers). Если изменяемые CSS свойства не делают этого, то мы можем вынести операции по вычислению стилей в отдельный поток. Наиболее известное свойство - это CSS Transform, которое выводит элемент в отдельный слой. Если элемент представляет из себя отдельный слой, то вычисление каждого следующего кадра может быть сделано на графическом процессоре (GPU). Это радикально улучшает производительность, особенно на мобильных устройства. Подробности здесь: OffMainThreadCompositing.

Вы можете отключить выведение анимации в отдельный поток, чтобы посмотреть, как эта особенность влияет на FPS. Для этого в настройках Firefox найдите флаг layers.offmainthreadcomposition.async-animations. И переключите его в false.

После выключения этой опции вы увидите, что FPS при использовании CSS стал таким же, как и при использовании JS.

Итог

Браузеры способы оптимизировать рендеринг не только программно, но и аппаратно. В целом, вам нужно стараться использовать CSS transitions/animations везде, где это возможно. Если же ваши анимации действительно сложны - помните, что писать анимацию на JavaScript нужно только с использованием requestAnimationFrame() .