Если вы когда-либо на страницах сайтов замечали "мерцание" элементов при выполнении таких операций CSS, как transform или animation, скорее всего, вы уже столкнулись с термином "аппаратное ускорение".

CPU, GPU, и Аппаратное ускорение

Вкратце, понятие "аппаратное ускорение" означает, что графический процессор (GPU - Graphics Processing Unit) будет помогать вашему браузеру в отрисовке страницы, выполняя некоторую тяжелую работу, вместо того, чтобы перекладывать все это на центральный процессор (CPU - Central Processing Unit). Когда операция ускоряется  средствами GPU, скорость её работы улучшается, равно как и ускоряется отрисовка страницы.

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

Аппаратное ускорение основано на многоуровневой модели, используемой браузером при отображении страницы. Когда определенные CSS-операции (например, 3D-transforms) применяются на каком-нибудь элементе страницы, этот элемент помещается на отдельный свой собственный "слой", где он будет отрисовываться независимо от остальной части страницы, но в то же время будет составной ее частью. Таким образом, при его преобразовании остальную часть страницы не нужно будет повторно отрисовывать, что обеспечивает значительное повышение скорости. Однако стоит упомянуть, что только 3D-преобразования подходят для создания своего собственного слоя; 2D-преобразования - нет.

Такие CSS свойства, как animation, transform и transition не ускоряются автоматически с помощью графического процессора, а вместо этого выполняются из более медленного программного механизма рендеринга браузера. Однако некоторые браузеры таки обеспечивают аппаратное ускорение при использовании определенных свойств. Например, свойство opacity - одно из немногих свойств CSS, которое можно должным образом ускорить, т.к. GPU может легко им управлять. Таким образом, если вы хотите использовать opacity для элемента, задействовав при этом transition или animation, браузер перенесет эту операцию на графический процессор и выполнит там манипуляции, что будет очень быстро. Из всех свойств CSS opacity - одна из самых эффективных, так что у вас не возникнет проблем с ее использованием. Другими распространенными операциями с аппаратным ускорением являются 3D-преобразования.

Хаки: translateZ или translate3d

В течение некоторого времени для аппаратного ускорения использовали такие хаки, как translateZ() и translate3d(), чтобы обмануть браузер и заставить его запустить нашу анимацию с использованием GPU. Делали это просто добавив 3D-преобразование к элементу, который на самом деле не будет преобразовываться. Например, элемент, который анимирован в двухмерном пространстве, можно ускорить, добавив к нему следующее простое правило:

transform: translate3d(0, 0, 0);

Аппаратное ускорение операции приводит к созданию отдельного слоя, который обрабатывается с помощью GPU. Однако создание такого слоя не всегда может быть решением некоторых узких мест производительности на странице. Методы создания слоев могут повысить скорость страницы, но они имеют свою цену: они занимают память в системной ОЗУ и на графическом процессоре (что особенно ограничено на мобильных устройствах), и большое количество таких элементов может вызвать плохие последствия (особенно на мобильных устройствах), поэтому их нужно использовать с умом, и вы должны быть уверены, что аппаратное ускорение вашей операции действительно поможет производительности вашей страницы.

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

Свойство will-change

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

Как упоминалось ранее, при использовании, например, 3D-преобразований для элемента, элемент и его содержимое будут помещены в отдельный слой, до того, как они будут отрисованы. Однако установка элемента в новом слое - относительно дорогая операция, которая может задержать начало анимации, вызывая то самое еле заметное "мерцание".

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

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

will-change: transform;

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

will-change: transform, opacity;

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

Влияет ли will-change на элемент, к которому применяется, помимо оповещения браузеру об изменениях?

Ответ - и да и нет. Это зависит от свойств, которые вы указываете браузеру. Если какое-либо не дефолтное значение свойства создаст контекст наложения для элемента, указание этого свойства в will-change создаст контекст наложения для элемента.

Например, свойство clip-path и свойство opacity оба приводят к созданию контекста наложения для элемента, к которому они применяются, когда они используются со значениями, отличными от дефолтных. Следовательно, использование одного из (или обоих) этих свойств в качестве значений для will-change создаст контекст наложения в элементе даже до того, как изменение действительно произойдет. То же самое относится и к другим свойствам, которые создают контекст наложения элемента.

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

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

Когда использовать will-change

Зная, что делает will-change, может возникнуть соблазн подумать: "Пусть браузер оптимизирует ВСЁ!». И вроде как это имеет смысл, правда? Кто бы не хотел, чтобы все изменения элементов были оптимизированы и готовы к внедрению по запросу?

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

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

Не используйте will-change для объявления изменений слишком большого количества свойств или элементов

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

*,
*::before,
*::after {
  will-change: all;
}

Как бы хорошо это ни выглядело, на самом деле это очень вредно и тем более недействительно. Ключевое слово all не только является недопустимым значением для will-change, но и вцелом бесполезно. В этом случае браузер уже пытается оптимизировать все, насколько это возможно, поэтому дальнейшее явное указание ему свойств ничего не поменяет. Это может нанести большой вред, потому что такие глобальные оптимизации используют много ресурсов машины, что может вызвать замедление страницы или даже сбой.

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

Предоставьте браузеру достаточно времени для работы

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

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

Например, мы ходим осуществить преобразование при наведении:

.element:hover {
  will-change: transform;
  transition: transform 2s;
  transform: rotate(30deg) scale(1.5);
}

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

Допустим, если элемент изменится при нажатии на него, то настройка will-change при наведении на этот элемент даст браузеру достаточно времени для оптимизации. Времени между наведением курсора на элемент и фактическим щелчком по нему пользователем достаточно, чтобы браузер настроил оптимизацию. Временное окно подобной операции около 200мс, что достаточно для настройки оптимизаций.

.element {
  transition: transform 1s ease-out;
}
.element:hover {
  will-change: transform;
}
.element:active {
  transform: rotateY(180deg);
}

Однако, если изменение должно произойти при наведении курсора, а не при нажатии, вышеупомянутое объявление  стилей будет бесполезным. Для этого случая подойдёт другой способ дать браузеру время на оптимизацию - указать изменения элемента, как объекта, являющимся предком своего родителя:

.element {
  transition: opacity .3s linear;
}
.parent:hover .element {
  will-change: opacity;
}
.element:hover {
  opacity: .5;
}

Удаляйте will-change после внесения изменений

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

Таким образом, вы всегда должны помнить об удалении will-change после того, как элемент будет изменен, чтобы браузер мог восстановить ресурсы, потраченные оптимизацией.

Невозможно удалить will-change, если он объявлен в таблице стилей, поэтому почти всегда рекомендуется устанавливать и отключать его с помощью JavaScript. С помощью скрипта вы можете объявить свои изменения в браузере, а затем удалить will-change после того, как изменения будут совершены. Например, так же, как мы это делали в стилях в предыдущем разделе, вы можете прослушивать, когда на элемент (или его предка) наведен курсор, и установить will-change при mouseenter. Если ваш элемент анимируется, вы можете прослушать, когда анимация закончилась, используя событие DOM animationEnd, а затем удалить will-change.

// Обозначим элемент, который будет анимирован по клику
var el = document.getElementById('element');

// Установим ему will-change при наведении
el.addEventListener('mouseenter', hintBrowser);
el.addEventListener('animationEnd', removeHint);

function hintBrowser() {
  this.style.willChange = 'transform, opacity';
}
function removeHint() {
  this.style.willChange = 'auto';
}

Экономно используйте функцию will-change в таблицах стилей

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

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

.sidebar {
  will-change: transform;
}

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

Значения свойства will-change

Свойство will-change принимает одно из четырех возможных значений: auto, scroll-position, contents и <custom-ident>.

Значение <custom-ident> используется для указания одного или нескольких свойств, которые вы ожидаете изменить. Несколько свойств разделяются запятыми. Примеры деклараций will-change:

will-change: transform;
will-change: opacity;
will-change: top, left, bottom, right;

Значение <custom-ident> исключает ключевые слова свойства (none, all, auto, scroll-position, ...) и как упоминалось - объявление will-change: all недействительно и, следовательно, будет проигнорировано браузером.

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

Значение scroll-position указывает, что вы ожидаете изменить положение прокрутки элемента. Это значение полезно, потому что при использовании браузер подготовит и отобразит контент, выходящий за рамки того, что отображается в окне прокрутки. Браузеры часто отображают только содержимое в окне прокрутки и часть содержимого за пределами этого окна, уравновешивая экономию памяти и времени из-за пропущенного рендеринга по сравнению с тем, чтобы прокрутка выглядела хорошо. Используя will-change: scroll-position, будет выполнена дальнейшая оптимизацию рендеринга, чтобы более быстрые прокрутки контента могли выполняться плавно.

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

Поддержка браузерами
chrome
Chrome
36
firefox
Firefox
36
internet explorer
IE
 
edge
Edge
79
safari
Safari
9.1
opera
Opera
24