Приёмы создания плавной анимации

Современные интернет-пользователи хотят, чтобы открываемые страницы были интерактивными и работали плавно. Страницы должны не только быстро загружаться, но и хорошо работать: прокрутка должна быть быстрой, а анимация и взаимодействия – плавными. Для того чтобы писать производительные сайты и приложения, необходимо понимать, каким образом браузер обрабатывает HTML, JavaScript и CSS. А уже на основе этих знаний писать код так, чтобы он работал как можно эффективнее.

60 кадров в секунду и частота обновления экрана устройства

Большинство устройств обновляют свои экраны 60 раз в секунду. Если выполняется анимация или переход, либо если пользователь прокручивает страницы, браузеру нужно соответствовать частоте обновления экрана устройства и выдавать по одной новой картинке (или кадру) при каждом обновлении экрана.

Каждый из этих кадров может длиться чуть более 16 мс (1 секунда / 60 = 16,66 мс). В реальности же браузеру нужно выполнить ещё некоторые действия, потому вся работа должна занимать не более 10 мс. Если не уложиться в эти рамки, то частота кадров будет меньше, а контент начнёт дёргаться на экране. Часто эту ситуацию называют подвисанием, она отрицательно сказывается на восприятии пользователей.

Конвейер пикселей

frame-full
Рис. 1. Конвейер вывода пикселей на экран

Существует пять основных составляющих, являющихся ключевыми точками конвейера вывода пикселей на экран:

1) JavaScript. Обычно JavaScript используется для выполнения работы, результатом которой будут визуальные изменения, будь то функция jQuery .animate(), сортировка набора данных или добавление DOM-элементов на страницу. Однако вызывать визуальное изменение можно не только с помощью JavaScript — также часто используются анимация CSS, переходы и API-интерфейс веб-анимации.

2) Вычисление стилей (Style). В процессе вычисления стилей определяется, какие правила CSS к каким элементам применяются с учётом соответствующих селекторов, например: .headline или .nav > .nav__item. Поэтому, после того как правила определены, они применяются и вычисляются итоговые стили для каждого элемента.

3) Расчёт макета (Layout). Как только браузер будет знать, какие правила применяются к элементу, он может начать вычислять, сколько места он займёт и где он находится на экране. Модель макета для Интернета означает, что один элемент может влиять на другие, например, ширина элемента <body> обычно влияет на значения ширины дочерних элементов и так далее по всему дереву, поэтому этот процесс для браузера может быть довольно сложным.

4) Прорисовка (Paint). Прорисовка – это процесс заполнения пикселей. Он подразумевает вывод текста, цветов, изображений, границ и теней, по сути – всех визуальных частей элементов. Прорисовка обычно выполняется на нескольких поверхностях, которые называются слоями.

5) Компоновка (Composite). Поскольку части страницы потенциально были прорисованы на нескольких слоях, они должны быть выведены на экран в надлежащем порядке, с тем чтобы страница отображалась правильно. Это особенно важно для элементов, которые перекрывают другие элементы, поскольку ошибка может привести к тому, что один элемент будет неправильно показан поверх другого элемента.

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

1. JS / CSS > Стиль > Расчет макета > Прорисовка > Компоновка

frame-full
Рис. 2. Полный конвейер

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

2. JS / CSS > Стиль > Прорисовка > Компоновка

frame-no-layout
Рис. 3. Конвейер без обработки макета

Если изменить свойство, которое связано только с прорисовкой, например фоновое изображение, цвет текста или его тень, другими словами, свойство, которое не влияет на макет страницы, браузер оставит макет неизменным, но ему всё равно придётся выполнить прорисовку.

3. JS / CSS > Стиль > Компоновка

frame-no-layout-paint
Рис. 4. Конвейер без обработки макета и прорисовки

Если же изменить свойство, которое не требует ни перерасчёта макета, ни прорисовки, браузер сразу же перейдёт к компоновке. Этот вариант является самым простым и наиболее предпочтительным для точек высокой нагрузки в жизненном цикле приложения, например при анимации или прокрутке.

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

Приём 1. Используйте свойства, вызывающие только компоновку, и контролируйте количество слоев

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

1.1. Для достижения анимационного эффекта изменяйте свойства transform и opacity

В самой производительной версии конвейера пикселей отсутствует перерасчёт макета и прорисовка. В ней выполняется только изменение компоновки. Чтобы добиться этого, необходимо изменять только те свойства, которые могут обрабатываться исключительно компоновщиком. На сегодня таких свойств только два — transform и opacity:

safe-properties
Рис. 5. Свойства transform и opacity

Помните, что для использования свойств transform и opacity элемент, для которого вы изменяете эти свойства, должен находиться на собственном слое. Чтобы создать слой, необходимо переместить на него элемент.

1.2. Перемещайте элементы, которые планируете анимировать, на отдельные слои с помощью will-change или translateZ

Элементы, которые планируется анимировать, следует переносить на отдельные слои (в пределах разумного, не злоупотребляя этим):

.moving-element {
will-change: transform;
}

Либо, для старых браузеров или тех, которые не поддерживают свойство will-change:

.moving-element {
transform: translateZ(0);
}

Таким образом браузер получает предупреждение о предстоящих изменениях. Поэтому он может подготовиться заранее, например создать отдельные слои.

1.3. Управляйте слоями и избегайте слишком большого их количества

Слои зачастую позволяют повысить производительность, поэтому может возникнуть соблазн разместить все элементы страницы на собственных слоях:

* {
will-change: transform;
transform: translateZ(0);
}

Этим вы заявляете, что хотите перенести на собственный слой каждый элемент на странице. Проблема заключается в том, что для каждого создаваемого слоя требуется память и управление, а это всё ресурсозатратно. На устройствах с ограниченным объемом памяти влияние на производительность может намного перевесить любые преимущества, связанные с созданием слоя. Текстуры каждого слоя необходимо загружать в графический процессор, поэтому имеются дополнительные ограничения по пропускной способности между графическим и центральным процессорами, а также памяти, которая есть в графическом процессоре. Поэтому не размещайте элементы на собственных слоях без надобности.

1.4. Используйте Chrome DevTools для анализа слоев в своем приложении

Чтобы понять, как работают слои в приложении и почему элемент размещен на отдельном слое, необходимо включить средство профилирования прорисовки на шкале времени Chrome DevTools:

paint-profiler-toggle
Рис. 6. Прорисовка в инструментах разработчика Chrome

Затем выполните запись. После завершения записи вы сможете щелкать отдельные кадры, которые находятся между столбцами, обозначающими количество кадров в секунду, и получать подробные сведения:

frame-of-interest
Рис. 7. Кадры в инструментах разработчика Chrome

После щелчка кадра в подробных сведениях появится новый элемент – вкладка слоев.

layer-tab
Рис. 8. Слои в инструментах разработчика Chrome

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

layer-view
Рис. 9. Анализ слоёв в инструментах разработчика Chrome

С помощью этого представления можно определить имеющееся количество слоев. Если у вас уходит много времени на компоновку при выполнении таких критически важных с точки зрения производительности действий, как прокрутка или переходы (целевой показатель равен 4–5 мс), то с помощью приведенной здесь информации можно определить, сколько у вас слоев, почему они были созданы, а затем уже заняться контролем числа слоев в приложении.

Приём 2. Упрощайте и сокращайте области прорисовки

Прорисовка – это процесс заполнения пикселей, которые затем будут скомпонованы и выведены на экраны пользователей. Зачастую этот процесс является задачей, которая выполняется в конвейере дольше всего и выполнения которой следует избегать при любой возможности. Если запускается перерасчёт макета, всегда запускается и прорисовка, поскольку изменение геометрии элемента означает, что его пиксели нужно привести в порядок. Прорисовка также запускается при изменении свойств, не связанных с геометрией, таких как фон, цвет текста или тени.

2.1. Перемещайте на отдельные слои элементы, которые двигаются или исчезают с экрана

Прорисовка не всегда формирует одно изображение в памяти устройства. При необходимости браузер может сформировать несколько изображений или слоев для компоновки.

layers
Рис. 10. Слои в компоновке

Преимущество этого подхода состоит в том, что элементы, которые регулярно заново прорисовываются или двигаются на экране с помощью свойств transform, можно обрабатывать, не затрагивая другие элементы. Это тоже самое, что и работа с графикой в Sketch, GIMP или Photoshop, где отдельные слои можно обрабатывать и располагать друг на друге для создания итогового изображения. Лучший способ создания новых слоев – это использование свойства CSS will-change. Такой способ работает в Chrome, Opera и Firefox. Для браузеров, которые не поддерживают это свойство, но будут работать быстрее, если создавать слои, таких как Safari и Mobile Safari, необходимо воспользоваться (немного неправильно) трёхмерным преобразованием для принудительного формирования нового слоя:

.moving-element {
will-change: transform;
transform: translateZ(0);
}

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

2.2. Сокращайте области прорисовки

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

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

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

2.3. Снижайте сложность прорисовки

При выполнении прорисовки одни операции требуют больше ресурсов, чем другие. Например, всё, что связано с событием blur (тень и т. п.), будет прорисовываться дольше чем, скажем, красный квадрат. Однако для CSS это не всегда очевидно: строки background: red; и box-shadow: 0, 4px, 4px, rgba(0,0,0,0.5); не выглядят так, что по производительности они значительно отличаются, но на самом деле это так и есть.

profiler-chart
Рис. 11. Профилировщик прорисовки

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

2.4. Для быстрого определения проблем с прорисовкой используйте Chrome DevTools

С помощью режима разработки Chrome можно быстро определить области, на которые распространяется прорисовка. Перейдите в режим разработки и нажмите на клавиатуре клавишу Escape. На открывшейся панели перейдите на вкладку прорисовки и установите флажок «Show paint rectangles» (Показать прямоугольники прорисовки):

show-paint-rectangles
Рис. 12. Отображение прорисовки

Когда установлен этот параметр, экран в Chrome будет мигать зеленым каждый раз, когда выполняется прорисовка. Если зеленым мигает весь экран или области экрана, которые предположительно не должны были прорисовываться, то следует провести более детальный анализ.

show-paint-rectangles-green
Рис. 13. Профилировщик прорисовки

Чтобы получить более подробные сведения, необходимо включить профилировщик прорисовки, установив флажок «Paint» вверху окна. Важно, чтобы этот флажок находился в установленном состоянии только при профилировании проблем с прорисовкой, поскольку, когда он установлен, потребляются дополнительные ресурсы и профилирование производительности даст искаженные результаты. Лучше всего использовать это средство, когда вам требуются сведения о том, какие объекты на странице были прорисованы.

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

paint-profiler-button
Рис. 14. Протокол прорисовки

При щелчке профилировщика прорисовки открывается представление, в котором указано, какие объекты подверглись прорисовке, сколько это заняло времени, а также отдельные вызовы прорисовки, которые потребовались:

paint-profiler
Рис. 15. Детализатор прорисовки

Этот профилировщик позволяет определить и область, и сложность прорисовки (фактически это время, которое занимает прорисовка). А это именно те аспекты, которые необходимо исправлять, если избежать прорисовки совершенно невозможно.

По материалам статей Paul Lewis 1, 2, 3.

Поделиться: