Как писать эффективные с точки зрения производительности CSS-селекторы
Эффективный по производительности CSS — не то чтобы особо новая тема, или особо важная, но она мне интересна и я следил за ней все более и более внимательно, с тех пор как начал работать в Sky.
Очень многие люди забывают или просто не осознают, что CSS может различаться по производительности. Впрочем, это можно легко простить, если вы понимаете насколько, в принципе, сложно написать низкопроизводительный CSS.
Все эти правила касаются, в общем-то, только очень посещаемых сайтов, для которых скорость действительно важна и на каждой странице могут быть тысячи DOM-элементов. Но, в конце концов, лучшая практика - есть лучшая практика, и не имеет значения: разрабатываете ли вы второй Facebook или сайт для местного дизайнера интерьеров. Ее стоит знать в любом случае.
CSS-селекторы
Для большинства из нас в CSS-селекторах нет ничего нового. Базовые селекторы —
это тип (например, div
), ID (например, #header
) и класс (например, .tweet
).
Менее распространенные селекторы включают базовые псевдоклассы (такие как :hover
),
селекторы CSS3 и селекторы на основе регулярных выражений, такие как :first-child
и [class^="grid-"]
.
У селекторов есть встроенное поведение относительно производительности, и, ссылаясь на Стива Саудерса (Steve Souders), отсортировать CSS-селекторы от более к менее производительным можно, например, так:
- ID:
#header
- Класс:
.promo
- Тип:
div
- Соседний элемент того же уровня:
h2 + p
- Дочерний элемент:
li > ul
- Вложенный элемент:
ul a
- Общий селектор:
*
- Атрибут:
[type="text"]
- Псевдоклассы/-элементы:
a:hover
Цитата из книги «Как сделать сайты еще быстрее» (Even Faster Websites) Стива Саудерса
Важно отметить, хотя селекторы по ID технически быстрее, с точки зрения производительности, разница очень невелика. С помощью CSS Test Creator Стива Саудерса можно видеть, что у селекторов по ID и селекторов по классу, в действительности, очень незначительная разница во времени перерисовки страницы.
В Firefox 6 под Windows я получил среднее время перерисовки страницы с простым селектором по классу в 10.9 мс. То же самое, но с селектором по ID, дало мне 12.5 мс, так что, в дейтвительности, с ID страница перерисовывалась медленнее, чем с классом.
Разницы в скорости между селектором по ID и по классу практически не существует.
Однако, тест селектора по типу (<a>
), в отличие от селекторов по классу или ID,
выдал гораздо большее время перерисовки страницы.
Тест на серьезно перенасыщенном селекторе с вложенными элементами дал около 440 мс!
Становится очевидным, что разница между селекторами по ID/классам и типами/вложенным элементам достаточно велика… Разницой же между ними самими можно пренебречь.
Обратите внимание: Эти цифры могут значительно отличаться на разных компьютерах и в разных браузерах. Настоятельно рекомендую протестировать ваш CSS самостоятельно.
Совмещаем селекторы
У вас могут быть независимые селекторы (например, #nav
), которые выберут любой
элемент с ID ‘nav’, или вы можете поставить в свой документ комбинированные
селекторы, например, #nav a
, который выберет все ссылки внутри любого элемента с
ID ‘nav’.
Итак, мы читаем их слева направо. Мы видим, что мы сперва ищем #nav
, а потом —
все элементы внутри него. Браузеры читают селекторы наоборот: справа налево.
Там, где мы видим #nav
c a
внутри, браузеры видят a
внутри #nav
. Эта тонкая
разница имеет огромное влияние на производительность селекторов, и ее просто
необходимо знать.
Чтобы докопаться до сути почему происходит именно так, посмотрите вот это обсуждение на Stack Overflow.
Для повышения производительности браузеру гораздо эффективней начинать с крайнего правого элемента (того, к которому браузер определенно применит стили) и затем подниматься наверх по дереву DOM, чем начинать наверху и потом спускаться по дереву вниз так, что можно вовсе не дойти до крайнего правого селектора, который также называют ключевым селектором.
Это имеет большое влияние на производительность CSS-селекторов…
Ключевой селектор
Ключевой селектор, как говорилось выше, это крайняя правая часть CSS-селектора. Это то, что в первую очередь видит браузер.
Помните мы обсуждали какие типы селекторов наиболее эффективны с точки зрения производительности? Итак, на производительность селектора в первую очередь влияет ключевой селектор, когда вы пишете быстрый CSS, то именно ключевой селектор является, хм, ключом к производительности CSS.
Такой ключевой селектор…
#content .intro{}
…вероятно, достаточно производителен, поскольку классы сами по себе являются производительным селектором. Браузер найдет все элементы .intro (которых, скорее всего, будет немного), а потом пройдет вверх по дереву DOM проверить, находится ли этот ключевой селектор внутри элемента c ID ‘content’.
Однако, именно такой селектор не силен в плане производительности:
#content *{}
Этот селектор делает вот что: смотрит на каждый элемент страницы (абсолютно
каждый), а потом смотрит, находятся ли какие-нибудь из них внутри родительского
элемента #content
. Это очень непроизводительный селектор, поскольку его ключевой
селектор является очень дорогим.
Зная это, мы можем принять более продуманные решения с точки зрения назначения классов и выбора элементов.
Предположим, что у нас есть огромная страница, она гигантских размеров, а еще у
нашего сайта просто огромная посещаемость. На этой странице сотни или даже тысячи <a>
.
Кроме того, есть маленький раздел со ссылками на социальные сети внутри <ul>
с ID #social.
Скажем к примеру, что там идут ссылки на Twitter, Facebook, Dribbble и Google+. Итак, у
нас на этой странице четыре ссылки на соцсети и сотни других ссылок.
В этом случае такой селектор будет неразумно дорог:
#social a{}
Что же произойдёт в этом случае? Браузер получит все тысячи ссылок на этой странице и
проверит их перед тем, как выбрать четыре ссылки внутри элемента #social
. Наш
ключевой селектор соответствует слишком большому количеству элементов, которые
на самом деле нам не нужны.
Для того чтобы побороть это - мы можем добавить более явный и специфический селектор,
добавив класс .social-link
к каждому из элементов <a>
в области #social
. Но это
противочерит тому, что нам известно: мы ведь знаем, что не нужно добавлять лишние
классы к элементам, когда мы можем использовать более чистую разметку с меньшим
количеством кода.
Вот почему тема производительности кажется мне настолько интересной: это странный баланс между передовыми практиками веб-стандартов и просто скоростью.
Итак, если обычно мы имели:
<ul id="social">
<li><a href="#" class="twitter">Twitter</a></li>
<li><a href="#" class="facebook">Facebook</a></li>
<li><a href="#"class="dribble">Dribbble</a></li>
<li><a href="#" class="gplus">Google+</a></li>
</ul>
с таким CSS:
#social a{}
То теперь мы напишем:
<ul id="social">
<li><a href="#" class="social-link twitter">Twitter</a></li>
<li><a href="#" class="social-link facebook">Facebook</a></li>
<li><a href="#" class="social-link dribble">Dribbble</a></li>
<li><a href="#" class="social-link gplus">Google+</a></li>
</ul>
С таким CSS:
#social .social-link{}
Этот новый ключевой селектор будет находить гораздо меньше элементов, что означает, что браузер сможет найти их и отобразить стили быстрее, а потом уже заниматься другими делами.
И на самом деле мы можем сократить этот селектор еще больше, чтобы он не был
перенасыщенным, вплоть до .social-link{}
. Об этом читайте уже в следующем разделе…
Итак, повторюсь: ваш ключевой селектор — то, что определяет сколько работы будет у браузера, так что обращайте на него внимание.
Перенасыщенные селекторы
Итак, теперь мы знаем, что такое ключевой селектор, и что отсюда идет бóльшая часть работы браузера. Теперь мы можем оптимизировать дальше. Самое лучшее в хороших явных ключевых селекторах — это то, что вы можете обойтись без перенасыщенных селекторов. Перенасыщенный селектор выглядит примерно так:
html body .wrapper #content a{}
Здесь очень много всего происходит, и по крайней мере три селектора из имеющихся - решительно не нужны. По крайней мере можно было бы написать то же самое, примерно так:
#content a{}
И что?
Итак, первый по порядку селектор означает, что браузер должен найти все элементы, a
затем проверить их наличие в элементе с ID content
и так далее, и так далее,
вплоть до элемента html
. Браузер вынужден из-за этого делать слишком много проверок,
которые в действительности не нужны. Зная это, мы можем привести более реалистичные
примеры, как этот:
ul#nav li a{}
К такому виду:
#nav a{}
Мы знаем, что если a
находится внутри li
, то она должна находиться внутри #nav
,
так что мы сразу выбрасываем li
из селектора. Далее, раз #nav
— это ID, который
существует на странице в единственном экземпляре, то тип элемента, к которому
он применен, абсолютно неважен: значит, мы можем выбросить ul
.
Перенасыщенные селекторы заставляют браузеры работать больше, чем это нужно, и занимают драгоценное время. Сделайте ваши селекторы понятнее и быстрее, выбросив из них лишнее.
Насколько все это необходимо?
Короткий ответ: не очень.
Ответ подлинее: зависит от того, что вы разрабатываете. Если вы работаете над своим новым портфолио, то пишите чистый код, это важнее, чем производительность CSS-селекторов, которую вы вряд ли когда-либо заметите.
Если вы разрабатываете второй Amazon, где микросекунды в скорости работы страницы имеют значение, то — может быть, а может быть и нет.
Браузеры (даже мобильные браузеры) станут парсить CSS только быстрее. Вряд ли вы когда-либо заметите на сайте медленные CSS-селекторы, но…
Но
Но пока все остается по-прежнему, браузерам все равно приходится делать ту работу, о которой мы говорили, какими бы быстрыми они ни становились. Даже если вам не нужно (или вы просто не хотите) применять что-либо из описанного здесь, в любом случае это определенно следует знать. Помните, селекторы могут быть дорогими в плане производительности, и по крайней мере самых зубодробительных вам стоит по возможности избегать. То есть если внезапно вы поймали себя на том, что пишете что-то такое:
div:nth-of-type(3) ul:last-child li:nth-of-type(odd) *{ font-weight:bold }
То, наверное, вы делаете что-то неправильно.
Я сам все еще относительный новичок в вопросах производительности CSS-селекторов, так что если я что-то пропустил или вам есть что добавить, пожалуйста, пишите в комментариях!
Еще об эффективности CSS-селекторов с точки зрения производительности
Я очень рекомендую сайты и книги Стива Саудерса. Это практически единственная рекомендация для дальнейшего чтения, и она вам реально пригодится. Парень шарит в своей теме!