Оптимизация скорости пересчета стилей только с помощью CSS

Оптимизация скорости пересчета стилей только с помощью CSS

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

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

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

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

Позже в этой статье мы рассмотрим CSS-анимацию как специфический случай проблемы пересчета стилей, сравнив оптимальные и неоптимальные примеры анимации. Вы можете посмотреть нашу демонстрацию анимации на CodePen.

Понимание работы отрисовки браузера

Когда веб-страница в первый раз загружается, браузер смотрит на HTML и создает древо объектов документа (DOM). Затем он смотрит на CSS и применяет его правила к соответствующим селекторам в этом древе DOM. Наконец, он выполняет JavaScript-код и отображает страницу.

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

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

Что такое инвалидация стилей?

Инвалидация - это процесс, который выявляет и помечает все элементы, которые требуют переоформления после изменения DOM.

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

Изменение DOM также можно описать как мутацию DOM. Это включает в себя действия, такие как переключение имени класса, добавление или удаление элемента, анимации наведения и так далее.

Типы инвалидации

Существует два типа инвалидации: мгновенная и ожидающая.

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

dropdownMenu.classList.toggle('active');

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

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

Что такое пересчет стилей?

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

.dropdown {
	display: none;
	position: absolute;
	left: 0;
	top: 100%;
	background-color: #22232e;
}
/* Показывать выпадающее меню при нажатии на ссылку */
.dropdown.active {
	display: block;
}

Первый набор CSS-правил устанавливает значение display в none. Это становится недействительным, когда вы нажимаете на ссылку, потому что у нас теперь есть новый селектор класса — active — с отображением, установленным в block. Затем браузер находит и применяет новое CSS-правило перед отображением страницы:

// Показать/скрыть выпадающее меню при нажатии на ссылку
dropdownLink.addEventListener('click', function (event) {
	event.preventDefault(); // предотвратить переход по ссылке
	dropdownMenu.classList.toggle('active');
});

Движки отрисовки браузера

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

Blink служит движком отрисовки для Chrome и других браузеров на основе Chromium, таких как Opera и Edge. В то время как Gecko является движком отрисовки для Firefox, а WebKit — движком отрисовки для Safari.

Исследование компоновки, рисования и компоновки

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

Infographic Of Browser Rendering Pipeline Showing Steps In Order: Invalidation And Recalculation, Layout, Painting, Compositing, Webpage Is Visible

Изменения в DOM могут влиять на компоновку веб-страницы. Браузеру, возможно, придется вычислить новый размер или позицию элементов на странице. Некоторые CSS-свойства, которые могут вызвать это, включают margin, border и padding.

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

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

Оптимизация производительности стилей с помощью CSS

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

Используйте более компактные деревья DOM

Большие и глубокие деревья DOM могут замедлить производительность. Если есть слишком много элементов HTML, браузеру потребуется больше времени для отрисовки страницы:

<div id="container">
	<div class="header">
		<h1>Это заголовок</h1>
	</div>
	<div class="content">Это абзац</div>
	<div class="footer">Это нижний колонтитул</div>
</div>

Использование более семантических элементов вместо постоянного использования div может помочь создать более компактное дерево DOM:

<header>
	<h1>Это заголовок</h1>
</header>
<article>Это абзац</article>
<footer>Это нижний колонтитул</footer>

Иметь более компактное дерево DOM также снизит количество инвалидации после мутации. Семантика помогает браузеру понять назначение элемента и ускорить его отображение. Код также становится более читаемым.

Уменьшите размер ваших таблиц стилей

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

Предположим, что у header и footer одинаковый цвет фона:

header {
	background-color: #00c2cb;
}
footer {
	background-color: #00c2cb;
}
/Альтернатива/ header,
footer {
	background-color: #00c2cb;
}

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

:root {
	--bg-color: #00c2cb;
}

header,
footer {
	background-color: var(--bg-color);
}

Препроцессор, такой как Sass, также может дополнительно поддерживать этот процесс.

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

Оптимизируйте использование селекторов

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

Поэтому лучше быть специфичными при использовании селекторов — например, использовать селекторы класса и идентификатора для целирования элементов. Примеры неспецифичных селекторов включают *, div, p, a и так далее.

Также избегайте селекторов потомков, так как они могут потребовать от браузера инвалидации многих дочерних элементов. Если вы хотите выбрать вложенный элемент, вам придется использовать правило вида .a .b {…}:

<div>Лорем, ипсум долор сит амет консектетур адиписичинг элит.</div>

Если вы хотите стилизовать эти элементы, вот как это можно сделать:

div {
/CSS-правила/
}
div p {
/CSS-правила/
}

С изменением браузер должен будет инвалидировать как родительские, так и дочерние элементы. Как и в предыдущем примере, это означает два набора инвалидации. Браузер читает справа налево, по мере продвижения вверх по DOM, поэтому сначала он найдет элемент p и затем перейдет к div.

В отличие от этого, с одним конкретным селектором браузер может сосредотачиваться только на его соответствии CSS-правилам:

<div>
	<p class="text">Лорем, ипсум долор сит амет консектетур адиписичинг элит.</p>
</div>

С конкретным именем класса, вот как можно стилизовать абзац:

.text{
/CSS-правила/
}


Избегайте частых или больших мутаций DOM

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

Держитесь в пределах разумных рамок с анимациями или используйте их только для интерактивных элементов.

Сравнение оптимальных и неоптимальных анимаций CSS

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

Посмотрите следующую анимацию на CodePen.

Они выглядят одинаково, верно? Но браузер выполняет больше работы для отрисовки правого блока.

Для первого блока анимируется свойство transform, и оно транслируется вдоль оси Y на -20px. Для второго блока анимируется свойство margin-top и изменяется на -20px.

Вот HTML:

<body>
	<div class="box"></div>
	<div class="box-2"></div>
</body>

И вот CSS:

div {
	height: 250px;
	width: 200px;
	background: #00c2cb;
	border: 2px solid #22232e;
	border-radius: 20px;
	margin: 20px;
}
.box {
	animation: translate 1s infinite alternate;
}
.box-2 {
	animation: margin 1s infinite alternate;
}
@keyframes translate {
	100% {
		transform: translateY(-20px);
	}
}
@keyframes margin {
	100% {
		margin-top: -20px;
	}
}

На любом браузере на основе Chromium откройте инспектор и нажмите на три точки в правом верхнем углу вкладки. Выберите Дополнительные инструменты, затем Отрисовка и, наконец, Мигание при рисовании:

Screenshot Of Chromium Browser Devtools Showing How To Enable Paint Flashing

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

Вот запись анимации с выбранной опцией мигания при рисовании:

Two Animated Squares Side By Side With Paint Flashing Selected

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

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

Изменение оптимальной анимации на анимацию с transform:

Performance Data From Browser Devtools For Box Animated With Transform Property

Запись длится около пяти секунд. Обратите внимание, что мы устанавливаем снижение производительности CPU в 6 раз медленнее, чтобы имитировать работу пользователя на медленном устройстве или с медленным сетевым подключением.

Как видно, отрисовка заняла 778 миллисекунд, включая компоновку и сведение слоев. Рисование заняло 480 миллисекунд. Также имеется 1877 миллисекунд простоя.

Теперь рассмотрим данные для блока, анимированного с использованием свойства margin-top:

Performance Data From Browser Devtools For Box Animated Using Margin Top Property

Здесь имеется значительная разница в скорости. Время отрисовки сократилось до всего 12 миллисекунд, а рисование уменьшилось до 13 миллисекунд. При этом браузер простаивает в течение 4471 миллисекунд — это много времени простоя для браузера.

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

Small Section Of Margin Animation Performance Data In Browser Devtools Highlighted To Show When Style Recalculations Are Triggered And How Long They Take

Все это отсутствует в анимации с использованием свойства transform.

Это всего лишь один специфический случай, но другие свойства, которые не вызывают шаги компоновки или рисования, включают opacity и filter. Элемент с установленным значением display: none все равно виден браузеру, поэтому я бы посоветовал использовать opacity с самого начала.

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

Заключение

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

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