Как писать эффективные с точки зрения производительности CSS-селекторы

Эффективный по производительности CSS — не то чтобы особо новая тема, или особо важная, но она мне интересна и я следил за ней все более и более внимательно, с тех пор как начал работать в Sky.

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

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

CSS-селекторы

Для большинства из нас в CSS-селекторах нет ничего нового. Базовые селекторы — это тип (например, div), ID (например, #header) и класс (например, .tweet).

Менее распространенные селекторы включают базовые псевдоклассы (такие как :hover), селекторы CSS3 и селекторы на основе регулярных выражений, такие как :first-child и [class^="grid-"].

У селекторов есть встроенное поведение относительно производительности, и, ссылаясь на Стива Саудерса (Steve Souders), отсортировать CSS-селекторы от более к менее производительным можно, например, так:

Цитата из книги «Как сделать сайты еще быстрее» (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-селекторов с точки зрения производительности

Я очень рекомендую сайты и книги Стива Саудерса. Это практически единственная рекомендация для дальнейшего чтения, и она вам реально пригодится. Парень шарит в своей теме!