Семантика для CSS селекторов и комбинаторов

Original author: Chris Sealey
  • Translation
Синтаксис CSS несложен, и для понимания его совсем не нужно иметь степень доктора в области IT. Однако, это один из немногих популярных языков, который не является логичным в самом прямом смысле этого слова. В отличие от других языков веб-программирования, таких как JavaScript и PHP, в CSS проблемы не решаются с помощью обычной логики. Алгоритмы типа «если X, то сделать Y, в противном случае сделать Z» или «выбрать все Y, затем сделать с ними X» не могут быть осуществлены в таком языке, как CSS. Проще говоря, это язык, созданный для оформления, язык для дизайнеров, а не девелоперов. Некоторые из опытных программистов, с которыми я работал, именно по этой причине тратили много усилий на то, чтобы освоить CSS.

Обучение CSS начинается с классов и ID, а также с использования . и # для непосредственного обозначения элементов. Этого достаточно чтобы построить полнофункциональный веб-сайт, но это не достаточно гибкое решение в случае полной смены дизайна. Давайте взглянем на альтернативный подход к управлению такими труднодоступными элементами.

Соседний родственный комбинатор

Начнём с селектора, который удобен в сложных ситуациях, — с соседнего родственного комбинатора. Соседний родственный комбинатор обозначается соединением двух элементов с помощью знака +:

 h1 + p 

Это выделяет следующий p-элемент, расположенный сразу после h1-элемента в DOM. Типографическая теория предполагает, что мы должны использовать отступы в параграфах текста, но только если они следуют за другим параграфом. На практике это может быть использовано, чтобы сделать отступы во всех параграфах, кроме первого:
    p + p {
         text-indent: 1em;
    }

Это гораздо удобней, чем выделять первый параграф с помощью class=«first». Три строки, никаких классов и полная поддержка браузеров. Если вы располагаете тэги img, относящиеся к наполнению сайта, внутри тэгов p (как, собственно, и следует делать), можно просто отодвинуть их левые поля обратно с помощью негативного значения -1em:
  
    p + p img {
       margin-left: -1em;
    }

Довольно просто, правда? А что, если нам захочется выделить первую строку всех параграфов, которые стоят сразу после заголовков, не изменяя всех остальных параграфов? Опять-таки мы можем использовать класс представления. Простой селектор, сделанный из соседнего составного комбинатора, и псевдо-элемент справятся с задачей:
  
h1 + p::first-line {
    font-variant: small-caps;
    }

Примечание: псевдо-элемент :first-line принят в CSS 2.1, в CSS 3 используется запись ::, с целью установить различие между псевдо-классами и псевдо-элементами.

Наследственный комбинатор

Обычный протокол разметки – это помещение разделов в как-либо названном элементе в #page или #wrap:
  
<div id="page">
    <header></header>
    <article>
        <section id="main"></section>
        <aside></aside>
    </article>
    <footer></footer>
</div>

Вне зависимости от того, используете ли вы синтакис HTML 5 или XHTML 1.1, этот основной формат должен выглядеть для вас знакомо. Если ваш документ имеет фиксированную ширину в 960px, выровнен по центру и каждый элемент расположен горизонтально, ваш CSS скорее всего напоминает:
  
#page {
    width: 960px;
    margin: 0 auto;
    }
header,
article,
footer { width: 100%; }

Возможно, вы более точны в своей работе, и чтобы избежать нежелательных изменений в элементах верхнего уровня, используете:
  
#page header,
#page article,
#page footer { width: 100%; }

Есть способ получше. Мы все знакомы с универсальным селектором *. Если комбинировать его употребление с наследственным селектором, можно выбрать все элементы, являющиеся прямыми потомками #page, без какого-либо влияния на «внучатые» элементы:
  
#page > * { width: 100%; }

В будущем это поможет вам добавлять или удалять элементы верхнего уровня в документе. Возвращаясь к нашей начальной схеме разметки, это повлияет на элементы header, article и footer, но никак не коснется #main и всего остального внутри элемента article.

Селекторы атрибутов строки и подстроки

Селекторы атрибутов – одни из самых действенных. Они также существовали в CSS 2.1 и обычно использовались в форме input[type=«text»] или [href="#top"]. Но CSS3 предлагает более глубокий уровень контроля в форме строк и подстрок.

Примечание: до сих пор все, что мы обсуждали, относилось к стандарту CSS 2.1, но теперь мы вступаем на территорию CSS3.

Существует четыре основных селектора атрибутов строки, где ‘v’ = значение, ‘a’ = атрибут.
v – одно из списка значений, разделенных пробелом: element[a~=«v»]
a начинается с v: element[a^=«v»]
a заканчивается на v: element[a$=«v»]
a содержит значение: element[a*=«v»]

Потенциал селекторов атрибута строки фактически бесконечен, но отличным примером являются иконки. Скажем, у вас есть неупорядоченный список ссылок на профайлы в соцсетях:
  
<ul id="social">
    <li><a href="http://facebook.com/designfestival">Like on Facebook</a></li>
    <li><a href="http://twitter.com/designfestival">Follow on Twitter</a></li>
    <li><a href="http://feeds.feedburner.com/designfestival">RSS</a></li>
</ul>

Привести их в порядок так же просто, как сделать запрос через их href атрибут, чтобы найти ключевое слово. Мы можем расположить их таким образом:
  
#social li a::before {
    content: '';
    background: left 50% no-repeat;
    width: 16px;
    height: 16px;
    }
#social li a[href*="facebook"]::before {
    background-image: url(images/icon-facebook.png);
    }
#social li a[href*="twitter"]::before {
    background-image: url(images/icon-twitter.png);
    }
#social li a[href*="feedburner"]::before {
    background-image: url(images/icon-feedburner.png);
    }

Аналогичным образом, можно выбрать все ссылки в PDF документах селектором атрибута Suffix:
  
a[href$=".pdf"]::before {
    background-image: url(images/icon-pdf.png);
    }

Браузеры, не поддерживающие атрибуты подстрок в CSS3, не покажут эти иконки, но это не так важно – они всего лишь красивое дополнение, без особой функциональности.

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

Наконец, мне хочется обрисовать выгодность использования псевдо-классов (не путать с псевдо-элементами и псевдо-классами link и state). Мы можем использовать их для выделения элементов по их позиции в DOM. Хорошим примером использования структурного псевдо-класса является выделение первого (или последнего) элемента в дереве элементов, или выбор между четными и нечетными элементами.
  
<ul>
    <li>List Item 1</li>
    <li>List Item 2</li>
    <li>List Item 3</li>
    <li>List Item 4</li>
    <li>List Item 5</li>
    <li>List Item 6</li>
</ul>
ul li { border-top: 1px solid #DDD; }
ul li:last-child { border-bottom: 1px solid #DDD; }
ul li:nth-child(even) { background: #EEE; }

Примечание: единственный доступный в CSS 2.1 псевдо-элемент — :first-child. Все остальные псевдо-элементы, включая :last-child, относятся к стандарту CSS3.

Однако необходимо знать, когда НЕ надо использовать структурные псевдо-элементы. Они должны быть использованы исключительно для выделения элемента по его позиции, а не по содержанию. Если требуется провести какое-либо действие над элементом вне зависимости от его расположения в DOM, используйте более многозначные семантические селекторы, например, class, ID или string.

Возможно, вы уже используете некоторые из вышеуказанных комбинаторов и селекторов – может, правильно, а может и нет, — но лишнее напоминание, в каких случаях они удобнее, чем class или ID, не повредит. Даже лучшие из нас часто ошибаются в подобных вещах.
Share post

Similar posts

Comments 47

    0
    Замечательно. Хотелось бы больше услышать про отличие ":" от "::".
      +4
      С одинарного двоеточия ":" начинаются псевдо-классы, например, a:hover. То есть, это неявный класс элемента a. Двойное двоеточие "::" — это псевдо-элементы, например, a::before. То есть, это неявный элемент, которого нет в разметке, но мы его как-бы создаем через css (точнее, браузер понимает, это мы выделяем что-нибудь в элемент).

      Подробнее про разные псевдо-элементы и псевдо-классы лучше читать в справочниках. Для начального ознакомления подойдет htmlbook.ru.
        +1
        А если Вы спрашиваете про отличия, например, :after от ::after, то тут все просто. В спецификации CSS2.1 использовалось одинарное двоеточие для псевдо-элементов, а в CSS3 стали применять двойное, чтобы отделить псевдо-элементы от псевдо-классов.
          0
          : — для псевдоклассов
          :: — для псевдоэлементов

          Но, для first-line, first-letter, before и after допустимы обе нотации. Обратная совместимось форева.
            0
            Интересно, а если использовать: и :: для одного класса, то боаузер перезапишет правила или создаст новый объект?

            PS: под рукой нет возможности протестировать.
          +2
          p + p img {
               margin-left: -1em;
          }

          А теперь дискотека:
          <p>…</p>
          <p>
          <img src='…' alt='…'><img src='…' alt='…'><img src='…' alt='…'>
          </p>


          Нельзя же так в лоб действовать.
            0
            Для наглядного примера самое то: )
              +2
              p + p img:first-child {
              margin-left: -1em;
              }

              ?
                +2
                Ну почти.
                <p>…</p>
                <p>
                <span><img src='…' alt='…'></span>
                <span><img src='…' alt='…'></span>
                <span><img src='…' alt='…'></span>
                </p>
                  0
                  ну если задача специфическая, то
                  p + p > img:first-child

                  хотя в таком случае лучше ходить по классам
              +2
              В примере с социальными иконками можно ::before заменить на метод с background-image и эта конструкция будет работать во всех браузерах, даже в ие7
                0
                Там вся соль в селекторах атрибутов, а не в before. В зависимости от ссылки добавляем иконку ту или иную иконку.
                  0
                  ну да
                  если заменить before на бекграунд, эта соль никуда не денется, и каждая ссылка будет со своей иконкой. зато будет работать почти во всех браузерах
                    0
                    а… ну я понял. Думал, что селекторы атрибутов вообще в малом количестве браузеров поддерживаются, а оказалось, что даже ie7 поддерживает, просто праздник
                0
                кстати, протестировать браузер на поддержку селекторов можно на сайте Криса Койера
                линк
                  +3
                  или посмотреть таблицу
                    0
                    На деле даже в последних «Хромах» (нестабильные не смотрел, правда) работают не все сочетания селекторов CSS3.
                  +2
                  Ну и неплохо бы закончить заметку (ибо на полноценную статью это не тянет) этой ссылкой, так как всегда есть нюансы, будь они неладны.
                    +3
                    Первый раз слышу чтобы у кого-то возникли проблемы при познаии CSS. Может дело в том, что его пытаются учить как «язык программирования», в то время как это обычные конфигурационные файлы с элементарным и довольно гибким синтаксисом?
                      –1
                      Это на первый взгляд все элементарно. При верстке сложных макетов часто всплывают довольно неочевидные нюансы, которые в книгах и спецификациях не упоминаются и побороть которые удается благодаря опыту/гуглению.
                      Плюс, даже самую простую html разметку можно стилями задизайнить десятком способов, и здесь мастерство в том, чтобы сделать это самым оптимальным и кроссбраузерным способом.
                      Да, CSS — одна из самых простых штук, что есть в вебе, но элементарной я бы ее не называл
                        +1
                        Это какие нюансы не упоминают с стандартах?
                          0
                          ок, упоминаются, но по-разному интерпретируются браузерами :)
                            0
                            Например каким? За всю жизнь никаких проблем не видел.
                              0
                              А какой у вас опыт, если не секрет?
                              Самый простой пример — бордеры у таблиц
                              вот здесь неплохо с примерами показано
                                0
                                Мда. Я думал, что этим комментарием отвечаю совсем на другой комментарий. Можно считать, что его не было.
                                0
                                Дробные значения округляются по-разному, например.
                                И вот ещё тема интересная была forum.myopera.net/showflat.php?Cat=&Number=42618&page=&view=&sb=5&o=&fpart=1&vc=1
                                  0
                                  Мда. Я думал, что этим комментарием отвечаю совсем на другой комментарий. Можно считать, что его не было…
                        +14
                        #page > * { width: 100%; }
                        Это из серии «Научи меня плохому»? Ну давайте вообще везде будем универсальный селектор ставить, а то сайты что-то слишком быстро открываться стали, да и процессорные мощности не используются на полную.
                          0
                          А с чего бы это должно тормозить? Т.к. здесь используется #, то разбор начнется слева направо и данный селектор будет работать быстро. Не вижу смысла пренебрегать универсальным селектором в ситуациях, в которых он удобен.
                            0
                            Кто же это Вам сказал, что разбор начнётся слева направо? Но даже если бы это было так, всё равно это очень медленный селектор, потому что будут обрабатываться _все_ элементы внутри #page.

                            The style system matches rules by starting with the key selector, then moving to the left (looking for any ancestors in the rule’s selector). As long as the selector’s subtree continues to check out, the style system continues moving to the left until it either matches the rule, or abandons because of a mismatch.
                            developer.mozilla.org/en/Writing_Efficient_CSS

                              0
                              Знакомая статья. В интернете по сути все только на нее и ссылаются. Во всяком случае иного я не нашел. Но основываясь на собственном опыте + том факте (если память не изменяет), что Джон Рейсиг в своем Сизле (имена и названия могу чуть исказить :) ) (движок селекторов в jQuery) при использовании id меняет порядок разбора начиная с нахождения этого самого id + до недавнего времени он работал в Mozilla.., могу предположить, что я был прав :). И этому могу предложить доказательства. Ах да, ремарка, "_все_ элементы внутри #page" — сие неверно, ибо используется дочерний селектор (">").

                              Так вот, я собрал небольшой тест: jsfiddle.net/E49EL/1/

                              Проверил только на Aurora 7.0a2 (2011-07-14) (версия Firefox для разработчиков).

                              Результат: «Serator прав на все 181мс». (погрешность в ~ 10 мс)

                              Интересно было бы посмотреть на результаты в иных браузерах + возможно вы что-то сможете предложить в ответ. На самом деле мне сей вопрос тоже очень интересен, ибо плохо документирован и, возможно, при различной структуре DOM-дерева результаты будут отличаться.
                                0
                                Всё может быть, разработчики браузеров что только не делают, чтобы поднять производительность.

                                Я в js понимаю примерно столько же, сколько в балете, но при заходе на тест в FireFox 5 получаю алерт «lampslave прав на все 52мс» :) Там точно скорость CSS, а не JS измеряется? Ещё интересным будет вариант с заведомо неправильным кодом, когда #id будет несколько одинаковых.

                                #id > * так или иначе проиграет другим селекторам, что по смыслу, что по неожиданным багам, что про удобству изменения. Независимые классы мне представляются намного более эффективными. Но вообще меня сейчас больше волнует скорость обработки [attr=«value»] против tag[attr=«value»].
                                  0
                                  Я использовал нативный поиск по селектору в js. По идее это тоже самое, что и в css, так что по времени оно по сути одно и тоже. + у меня достаточно медленный компьютер по современным меркам и, быть может, вам имеет смысл увеличить количество циклов выполнения скрипта, чтобы погрешность не была слишком малой (аль запустить скрипт несколько раз и посмотреть на тенденцию). В реальности же какой бы вариант не выиграл, по сути разница уже настолько мизерна, что в большинстве случаев этим можно пренебречь.

                                  Рассматривать баги браузеров, аль ошибки разметки я тоже не вижу смысла, ибо с универсальным селектором я багов уже не помню (может быть во времена ie6 они и были :) ), ну а кривая верста — увы :).

                                  Если бы кто-то провел полномасштабное исследование данного вопроса, то это было бы здорово, ибо исследования 2000-го года с несколькими правками на MDC (MDN) уже давно устарело, а на нем основаны все статьи-клоны в сети (иного я не нашел).

                                  В одном я уверен точно, что если универсальный селектор ("*") в какой-то ситуации предпочтительнее, то имеет смысл использовать его (при этом понимая то, каким образом браузер парсит селекторы, ну или хотя бы думая, что понимаешь :) ).
                                    0
                                    Может и так, но не факт. Секунды в целом сохраняются. Pentium® Dual-Core CPU 2210 @ 2.20GHz

                                    Не знаю, как там в жизни, а по тесту содружество #id > * показывает себя с очень хорошей стороны. Я удивлён. Не люблю повторять чужие глупости :(

                                    Да, было бы здорово, только вот кто это будет делать…

                                    В любой ситуации надо использовать предпочтительный вариант :) Просто предпочтения у всех разные.
                                  0
                                  И ещё вариант. У Вас 3 дочерних элемента. Посмотрите, что будет, если их станет 10 или 20.
                                    0
                                    Продублировал контейнер article c содержимым, теперь их 20. + увеличил количество циклов выполнения скрипта в 10 раз. Комп призадумался и выдал: «Serator прав на все 6597мс» в том же браузере, что и в прошлый раз.

                                    jsfiddle.net/xKX32/2/ — пробуйте.
                                      0
                                      Да, в этом виде отрыв увеличивается.

                                      Только вот Вы, кажется упустили одну деталь. Вы используете #id > * против #id tag. Если заменить этот «мой» селектор потомков на селектор дочерних элементов #id > tag, я остаюсь в выигрыше более чем на 2 секунды.
                                        0
                                        Я основывал свои опыты на примере из статьи, а там использовались именно такие селекторы. Но! Я изменил последний скрипт, уменьшил количество циклов в 10 раз (мой компьютер уж очень долго думал над прошлым скриптом :) ) и сменил селектор "#container header,#container article,#container footer" на "#container>header,#container>article,#container>footer".

                                        Мой результат на все том же браузере: «Serator прав на все 751мс».

                                        Новый скрипт: jsfiddle.net/ZwtyJ/
                                          0
                                          «lampslave прав на все 399мс»
                                          Видимо, у FF7 будет очень большой отрыв по производительности. И это здорово.
                            +7
                            Самая большая проблема CSS и кодеров — IE6.
                            Сколько лет верстаю, и уже отказался от поддержки IE6 очень давно, но все равно никак не могу перейти на использование всех CSS-селекторов в полной мере. Как запилилось в голову, что IE6 поддерживает их самый минимум и что нужно использовать этот минимум — так где-то и сидит…
                            Хотя и IE7 не блещет, а его пока приходится поддерживать, что тоже беда…
                              +1
                              Даже как-то «кто-то их поддерживает не в полной мере, потому только. # :hover и т. п.»
                                +1
                                Да все просто.
                                "*",".", "#", «elem» — используем только это, если у нас ИЕ6+.

                                Если у нас ИЕ7+ и выше, то можно также:
                                elem[attr=value] и все похожее, " > ", E+E, E ~ E :first-child. Вот, курите эти селекторы, это ща наиболее актуально. Как тока мы отказались от поддержки ИЕ6, я наоборот оч рад был, что наконец могу юзать эти селекторы.

                                Если у нас ИЕ8+, то добавляются:
                                :before, :after, :last-child, возможно что-то еще.
                                  0
                                  >> Возможно, вы более точны в своей работе, и чтобы избежать нежелательных изменений в элементах верхнего уровня, используете:
                                  Принципы Блок, Элемент, Модификатор (БЭМ)
                                    0
                                    Может, года через 3 это и станет актуально.
                                    Пока же даже с двумя классами у одного элемента (типа «first selected») — уже беда-печалька.
                                      0
                                      не советовал бы менять иконки в зависимости от урла, мне кажется это плохая идея, хоть и оригинальная. напишешь vk.com вместо vkontakte.ru или еще по какой причине урл изменится и иконка отвалится. юзайте старый добрый класс (или ид).
                                        0
                                        А вы не забыли ":not", который в CSS3?
                                        Очень удобно между прочим:
                                        a:not([href^="/"], [href*="gordio.pp.ua/"]) {
                                          padding-right: 18px;
                                          background: url("../img/external.png") right top no-repeat;
                                        }

                                        Only users with full accounts can post comments. Log in, please.