Фронтенд-разработчик
1,6
рейтинг
2 декабря 2014 в 16:11

Разработка → Оптимизируем производительность веб-страницы: 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. Спасибо за чтение!
Андрей Романов @andrew-r
карма
27,0
рейтинг 1,6
Фронтенд-разработчик
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (22)

  • +5
    Большинство из этих советов в настоящее время не слишком актуальны. Браузеры сейчас сами хорошо оптимизируют обработку css и отображение страницы.
    Как минимум о производительности селекторов сейчас точно практически нет смысла парится.

    Хотя минификация и склеивание, конечно всегда пригодятся. Особенно это касается склеивания, так как время на обработку лишнего запроса к серверу, может сильнее влиять на время отображения страницы, чем загрузка десяятка другого лишних килобайт
    • +2
      Не слишком актуален лишь совет о производительности селекторов, да.
      Он немного оторван от реальности, однако он вполне применим на крупных хайлоад-проектах. И да, лишним этот совет точно не будет — разработчики должны знать об этом, пусть и не применяя на практике до поры до времени.
      • +3
        Каскад сейчас тоже слабо влияет на время обработки css. Хотя знать как он работает в общих чертах, конечно, все равно полезно.
        Но большого выигрыша в производительности его оптимизация не даст. Полезнее следить за тем чтобы не раздувать файл стилей и не применять и перезаписывать одни и те же стили несколько раз. Для этого может оказаться полезным, например, такое расширение для хрома как css dig
        • +1
          Вот свежее исследование на эту тему benfrain.com/css-performance-revisited-selectors-bloat-expensive-styles/

          Вкратце, не стоит над этим заморачиваться.
          • +1
            Самое ценное там — замечание разработчика Вебкита. Которое говорит о том, что исследование полностью некорректно. Хотя верно, что заморачиваться обычно не стоит. Однако, когда встречается анимация и динамические изменения страницы, то тут скорость обработки селекторов в том числе может заметно сказаться.
  • 0
    Было бы интересно узнать абсолютные значения производительности селекторов.
  • 0
    Вот еще неплохая статья в тему. Лишний раз удобство Grunt'а демонстрируется.
  • +1
    Вы упомянули про отключение hover-состояний, но при этом не указали зачем это нужно делать. В действительности же, дело не в «интересе пользователя», а в том, что это может серьезно повлиять на процесс отрисовки страницы в момент скролла.

    На Хабре была статья на эту тему, она хорошо раскрывает описанный вами метод.
    • 0
      Поддерживаю.

      Кстати, статья похожа на рерайт вот этой статьи: http://frontender.info/pointer-events-60fps/
      • 0
        Или на перевод вот этой: http://www.thecssninja.com/javascript/pointer-events-60fps

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

    Что-то я не понял в этом месте, объясните пожалуйста. Если я часть стилей размещу в самом HTML, до подключения основных стилей в разделе , то как это поможет отобразить часть контента до их загрузки, ведь, как только браузер встретит линк на внешний стиль, рендеринг страницы остановится до полной загрузки стилей и никакой части контента пользователь все равно не увидит, так?
    • –1
      Имеется ввиду следующее: прописываете основные стили в тег style и размещаете его внутри тега head каждой страницы. А ссылку на основной css-файл ставите уже в теге body. При таком раскладе браузер будет загружать css-файл параллельно с другими элементами страницы. Негативный момент только один — подергивание страницы после загрузки css. Лично мне это не нравится чисто психологически, поэтому я по старинке ставлю ссылку на css-файл в head.
      • +3
        Ага, только вот для тега link расположение внутри body запрещено стандартом HTML )
        • 0
          Да, но сам гугл рекомендует вставлять не то что в , а даже после …
          Хотя, не думаю, что гугл интересуется валидностью своих сайтов — они же их и ранжируют.
      • 0
        Не представляю, каким образом можно ссылку на CSS-файл поместить внутри body. Обычно в самом верху страницы помещают инлайновые стили, а затем уже подключают внешние файлы.
        • 0
          «В самом верху страницы» — это где именно? Если вы перечисляете в особом порядке css-файлы, размещая их в head, то это все равно ничего не даст, т.к. все, что в head, блокирует отображение страницы вне зависимости от порядка следования. Чтобы этого избежать, ссылку на css нужно ставить вне тега head.

          К стати, я не очень понял ту критику, которая на меня свалилась. Как я написал выше, сам я так предпочитаю не делать. Но Гугл советует выносить ссылки на css-файлы из head. Хотя с другой стороны интересно, что на многих Гугловских сайтах эта рекомендация не выполняется.
  • 0
    В гугловском PageSpeed Insights можно получить практические советы по ускорению сайта. Касаемо css-стилей, приводится совет подключать css-файл после закрывающего «/html».

    Оригинальный файл small.css загружается после загрузки страницы. Порядок применения правил CSS определяется путем внедрения всех элементов style и link в документ с помощью JavaScript.


    Интересно было бы услышать вебмастеров, которые смогли решить проблему данным советом.
  • +1
    function loadCSS(e, t, n) {
        "use strict";
        var r = window.document.createElement("link");
        var i = t || window.document.getElementsByTagName("script")[0];
        r.rel = "stylesheet";
        r.href = e;
        r.media = "only x";
        i.parentNode.insertBefore(r, i);
        setTimeout(function() {
            r.media = n || "all"
        });
        return r
    }
    loadCSS("style.css");

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.