Быстрый удар точно в цель

CSS-селекторы

Краткое введение

Все наверняка знают, что такое селекторы…

.content h1 {
	font-size: 1.5em;

	}

Более строгое определение

Селекторы — это конструкции CSS-кода, сопоставляемые с элементами дерева HTML- или XML-документа с целью избирательного выделения в нем тех или иных узлов.

expression ∗ element → boolean

Спецификации

Принципиальные различия селекторов уровней 2, 3 и 4

Псевдоэлементы

CSS2:

E:hover {…}
E:before {…}

CSS3:

E:hover {…}
E::before {…}

Терминологические тонкости. Простой селектор

Пример: div, *, [foo="bar"], .content, #intro, :hover

Терминологические тонкости. Последовательность простых селекторов

Пример: div.content:hover

Терминологические тонкости. Комбинированный селектор

Примеры: div p, .menu > a:hover, h1 + p

Терминологические тонкости. Список селекторов

Пример: h1, h2, h3

Селекторы и производительность

Как браузер обрабатывает загружаемую страницу?

Наглядная демонстрация

Можно понаблюдать за процессом отрисовки страницы по мере ее загрузки в замедленном режиме

Основания для пересчета и возможной перерисовки

Отрисовка страницы при первоначальной загрузке

<body>
	<div id="page">
		<header id="intro">
			<h1>…</h1>
			<p>…</p>
		</header>

	</div>
</body>

Вначале нет ничего, кроме «пустого» элемента <body>

<body>
	<div id="page">
		<header id="intro">
			<h1>…</h1>
			<p>…</p>
		</header>

	</div>
</body>

Затем появляется «пустой» элемент <div>

<body>
	<div id="page">
		<header id="intro">
			<h1>…</h1>
			<p>…</p>
		</header>

	</div>
</body>

После этого — «пустой» элемент <header>

<body>
	<div id="page">
		<header id="intro">
			<h1>…</h1>
			<p>…</p>
		</header>

	</div>
</body>

Получили контент элемента <h1>, перерисовали требуемое

<body>
	<div id="page">
		<header id="intro">
			<h1>…</h1>
			<p>…</p>
		</header>

	</div>
</body>

Получили контент элемента <p>, вновь перерисовали требуемое

<body>
	<div id="page">
		<header id="intro">
			<h1>…</h1>
			<p>…</p>
		</header>

	</div>
</body>

Как анализируются селекторы?

body section.content h1 {…}

Всегда справа налево!

القاعدة

Является ли текущий элемент заголовком первого уровня?

body section.content h1 {…}

Если да, следуем вверх по DOM, ищем элемент <section> с классом content

body section.content h1 {…}

Нашли? Отлично! Следуем еще выше в поисках <body>

body section.content h1 {…}

Какие селекторы самые быстрые?

Очевидно, те, которые требуют меньше проверок:

#intro {…}
.content {…}

А вот такое будет работать очень-очень медленно, несмотря на селектор по id

#intro * {…}
Slowpoke is too slow

Такой селектор уже побыстрее

#intro h1 {…}

Здесь выше специфичность крайней справа части комбинированного селектора

Вот такой — еще быстрее

#intro > h1 {…}

В данном случае придется обрабатывать меньшее число узлов DOM

Избыточность не нужна!

header#intro {…}
#intro {…}

Это почти полностью справедливо и для селекторов по классу

Еще один примерчик устранения избыточности

body ul.navigation li a {…}
.navigation a {…}

Общие рекомендации по повышению производительности селекторов

См. Google Page Speed

И последние станут первыми?

Странное, казалось бы, дело:

Второй при «честной» реализации требует больше ресурсов

E:last-child не везде реализован «честно»

Можно сравнить отрисовку меню навигации в замедленном режиме в различных браузерах

Все лучшее — детям?

Давно и успешно реализованы:

Рассчитывать ли нам на селектор предка или селектор родителя?

Как это может выглядеть? Код разметки

<section class="content">
	<h1>Заголовок</h1>
	<p>Текст первого абзаца.</p>

	<p>Текст еще какого-то абзаца с <code>фрагментом кода</code>.</p>

</section>

Как это может выглядеть? Код таблицы стилей

.content:has(code) {
	line-height: 1.5em;
 }

Условия установления применимости правила

Дополнительные проблемы

Целевые объекты селектора

По умолчанию subjects определяются крайней справа частью селектора:

.content pre code {…}

Принудительное переопределение целевых объектов

ol > li:only-child {…}
ol! > li:only-child {…}

Одна и та же структура DOM-дерева, но различные целевые объекты

Тестирование производительности

Как выяснить порядок различий?

Устройство тестовых файлов

<div id="d1" class="d1">
	<section id="s1" class="s1">
		<article id="a1" class="a1">
			<p id="p1" class="p1"><span>Текст абзаца 1.</span></p>
			… еще 9
		</article>
		… еще 9
	</section>
	… еще 5
</div>
… еще 4

Методика измерения

Перед таблицей стилей, внутри <head>…</head>:

var $startTime = (new Date()).getTime();

Перед закрывающим тегом </body>:

window.onload = function() {
	var $processDuration = (new Date()).getTime() - $startTime;
}

Собственно, пища для размышлений

Профайлеры

Краткие выводы по результатам тестов

Каскадность vs. независимость

Наследование

body {
	font-size: 16px;
	}
.content h1 {
	font-size: 1.5em;
	}

Детально описывается в CSS2.1

Каскад

abbr {
	border-bottom: dotted .1em;
	}
h1 abbr {
	border-bottom: none;
	}

Детально описывается в CSS2.1

Специфичность

«Звездочка» игнорируется, :not сам по себе не рассматривается как псевдокласс

Примеры вычисления специфичности

Прямая цитата из спецификации Selectors Level 3:

*              /* a=0 b=0 c=0 -> specificity =   0 */
li             /* a=0 b=0 c=1 -> specificity =   1 */
ul li          /* a=0 b=0 c=2 -> specificity =   2 */
ul ol + li     /* a=0 b=0 c=3 -> specificity =   3 */
h1 + *[rel=up] /* a=0 b=1 c=1 -> specificity =  11 */
ul ol li.red   /* a=0 b=1 c=3 -> specificity =  13 */
li.red.level   /* a=0 b=2 c=1 -> specificity =  21 */
#x34y          /* a=1 b=0 c=0 -> specificity = 100 */
#s12:not(foo)  /* a=1 b=0 c=1 -> specificity = 101 */

Верстка независимыми блоками в идеале

Область применения

Индастриал такой

Собственно, о здравом смысле

Google Page Speed рекомендует:

ul li {color: blue;}
ol li {color: red;}
.unordered-list-item {color: blue;}
.ordered-list-item {color: red;}

Семантика?.. Объем HTML-кода?..

Вполне нормальная практика

.content h1 {

	}
.content p {

	}

Слабое место

Переопределить объявления, соответствующие селектору с высокой специфичностью:

.content p {margin-bottom: 1em;}

…можно только за счет еще более высокой специфичности:

.content .special {margin-bottom: 0;}

Тоже вполне нормальная практика

#intro {

	}

Идентификаторы или классы?

Что именно использовать? Решайте сами в зависимости от задачи!

Избранные новшества Selectors Level 3

Структурные псевдоклассы nth-…

Выбираем нечетные элементы:

E:nth-child(odd) {…}

Четные:

E:nth-child(even) {…}

Более сложные аргументы

Первый пример. E:nth-child(2n)

Второй пример. E:nth-child(5)

Более сложные аргументы

Третий пример. E:nth-child(3n+5)

Четвертый пример. E:nth-child(3n-5)

Более сложные аргументы

Пятый пример. E:nth-child(-4n+9)

Ну и хватит. Хорошего понемножку. :-)

Вспоминаем матан!

Аргумент — последовательность c(n) = an + b, где n — неотрицательное целое, a и b — целые

E:nth-child(2n) {…} /* 0-й, 2-й, 4-й, 6-й, … */
E:nth-child(5) {…} /* только 5-й */
E:nth-child(3n+5) {…} /* 5-й, 8-й, 11-й, … */
E:nth-child(3n-5) {…} /* -5-й, -2-й, 1-й, 4-й, 7-й, … */
E:nth-child(-4n+9) {…} /* 9-й, 5-й, 1-й, -3-й,  */

Будьте внимательны!

CSS:

.content p:nth-child(even) {…}

HTML:

<section class="content">
	<h1>Заголовок</h1>
	<p>Текст первого абзаца.</p>
	<pre><code>Образец кода</code></pre>
	<p>Текст второго абзаца.</p>
</section>

Выделяем элементы определенного типа

CSS:

.content p:nth-of-type(even) {…}

HTML:

<section class="content">
	<h1>Заголовок</h1>
	<p>Текст первого абзаца.</p>
	<pre><code>Образец кода</code></pre>
	<p>Текст второго абзаца.</p>
</section>

Псевдокласс target

<section class="chapter" id="some-chapter-name">…</section>
.chapter:target {…}
http://some-site/some-page#some-chapter-name

Интересная штука, реализовал у себя

Ссылки

Спасибо за внимание!