Оптимизируем производительность веб-страницы: CSS

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

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

Как страница выводится на экран

Давайте разберёмся в том, что происходит при открытии новой страницы.

Итак, вы нажимаете enter и браузер посылает запрос на сервер. В ответ сервер посылает запрашиваемую страницу.

  1. Браузер выполняет анализ полученной разметки. Формируются узлы (nodes), из которых затем строится DOM.
  2. Если браузер обнаруживает ссылки на таблицы стилей, он немедленно отправляет запрос на сервер и загружает файлы; при этом отрисовка страницы блокируется.
  3. Загрузив стили, браузер их анализирует и строит CSSOM.
  4. Когда DOM и CSSOM сформированы, браузер создаёт на их основе модель визуализации (rendering tree). В неё попадают только те элементы, которые будут выведены на экран.
  5. Для каждого элемента из модели визуализации рассчитывается его положение на странице. Этот процесс называется формированием макета.
  6. После окончания формирования макета браузер отрисовывает полученный результат (painting).

Можно много чего рассказать о процессе рендеринга страницы, однако в контексте этой статьи нас интересует второй пункт.

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

<link href="style.css">
<link href="style.css" media="screen">
<link href="style.css" media="(orientation: portrait)">
<link href="style.css" media="(max-width: 960px)">
<link href="style.css" media="print">

У ссылки на первый файл таблицы стилей нет никаких атрибутов, и браузер начнёт загрузку файла сразу же после обнаружения ссылки на него. У ссылки на второй файл указан атрибут media="screen". Этот атрибут браузер присваивает элементу <link> по умолчанию, если он отсутствует, так что второй файл будет обработан сразу же после первого. В ссылках на третий и четвёртый файлы содержится условный media-запрос. Если это условие выполняется, браузер начнёт обработку файла. Если оно не выполняется, то браузер отложит эти файлы «на потом», и сперва обработает более актуальные стили. В четвёртой ссылке в атрибуте media стоит значение «print», указывающее браузеру на то, что этот файл содержит стили для печати. Так как в данный момент браузеру они не требуются, он также отложит их обработку.

Методы оптимизации

С порядком обработки стилей мы разобрались, теперь ваше дело взять это себе на вооружение. Далее мы поговорим о методах оптимизации этапов отрисовки страницы (загрузка, обработка стилей).

Использование высокопроизводительных селекторов

Не стоит забывать, что разные селекторы обрабатываются браузером с разной скоростью. Стив Саудерс в своё время проводил исследование и рассортировал CSS-селекторы по производительности — от наиболее быстрых до медленных:

  • Идентификатор: #id
  • Класс: .class
  • Элемент: div
  • Соседний элемент: h2 + p
  • Дочерний элемент: li > ul
  • Вложенный элемент: ul li
  • Общий селектор: *
  • Атрибут: [type=«email»]
  • Псевдоклассы/псевдоэлементы: a:hover

Крайне не рекомендуется использовать общий селектор * для чего бы то ни было. К слову, выигрыш при использовании идентификатора вместо класса на деле совсем небольшой, так что можете использовать то, что вам удобнее.

Уменьшение каскада

Большой каскад пагубно влияет на производительность. Сравним две конструкции:

#header .header__inner nav ul.nav-menu li:hover a {}
 
#header li:hover a {}

Очевидно, что вторая конструкция будет обработана быстрее.

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

.social li a {}

В данном случае браузер сперва найдёт все ссылки на странице (только представьте, сколько времени уйдёт на обработку такого запроса на огромных страницах), затем выберет ссылки, вложенные в <li>, из полученных ссылок отсеет всё, что не вложено в элемент с классом .social. Рекомендую в таких случаях сокращать конструкцию до двух селекторов следующего вида:

.social .social_link {}

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

Минификация и склеивание CSS

Избегайте использования множества мелких CSS-файлов; хорошей практикой является «склеивание» всех файлов. Благодаря склейке файлов браузеру придётся делать один запрос к серверу вместо нескольких.

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

  • Сервис CSS Shrink — этим сервисом я пользуюсь постоянно.
  • Сервис CSS Compressor — по сравнению с предыдущим даёт бо̀льшие возможности для настройки.
  • Плагин для Gulp — CSSO
  • Плагин для Grunt — contrib-cssmin

 

Отключение :hover-состояний при прокрутке страницы

Некоторые сайты изобилуют интерактивными элементами, которые имеют уникальные :hover-состояния. Когда пользователь прокручивает страницу, его вряд ли интересует, что произойдёт, если навести курсор вон на ту кнопочку или на это поле ввода. Можно смело отключать :hover-состояния при прокрутке.

Простое решение — создать класс .disable-hover и добавлять его к <body> во время прокрутки.

.disable-hover {
  pointer-events: none;
}

 

var body = document.body,
    timer;
 
window.addEventListener('scroll', function() {
  clearTimeout(timer);
  if(!body.classList.contains('disable-hover')) {
    body.classList.add('disable-hover')
  }
 
  timer = setTimeout(function(){
    body.classList.remove('disable-hover')
  }, 500);
}, false);

 

Critical-path CSS

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

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

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

 

Заключение

Надеюсь, что эта статья была полезна для вас. Буду рад, если в комментариях вы опишете свои приёмы по оптимизации CSS. Спасибо за чтение!