Лучшие практики стилизации элементов сейчас можно выразить следующими тезисами:


  1. Старайтесь стилизовать элементы так, чтобы их визуализация не ломалась при перемещении их в другое место. Из этого принципа следует, что стоит минимизировать использование составных селекторов (например, вида header p a).
  2. Используйте пространства имён, чтобы минимизировать вероятность конфликтов правил относящихся к разным элементам. Это приводит к длинным именам, но избавляет от кучи проблем в будущем.

Итак, какие возможности для привязки стилей к элементам у нас есть сейчас? У любого элемента есть следующие характеристики:


  1. Имя элемента
  2. Идентификатор
  3. Набор классов
  4. Набор атрибутов
  5. Набор свойств

Давайте посмотрим, как мы можем их использовать, на примере простого списка задач, содержащего карточки с названием задачи и оценкой времени её выполнения...


Имя элемента


<my-task-list>
  <my-task-card>
      <my-task-card-title>Write HTML</my-task-card-title>
      <my-task-card-estimate>1 hour</my-task-card-estimate>
  </my-task-card>
  <my-task-card>
      <my-task-card-title>Write CSS</my-task-card-title>
      <my-task-card-estimate>2 hours</my-task-card-estimate>
  </my-task-card>
  <my-task-card>
      <my-task-card-title>Write JS</my-task-card-title>
      <my-task-card-estimate>10 hours</my-task-card-estimate>
  </my-task-card>
</my-task-list>

my-task-list {
  display: flex;
  flex-direction: column;
}

my-task-card {
  display: inline-flex;
  margin: .5rem;
  padding: .5rem;
  border: 2px solid gray;
  border-radius: .5rem;
}

my-task-card-title {
  margin: .5rem;
  font-weight: bolder;
  flex: 1 1 auto;
}

my-task-card-estimate {
  margin: .5rem;
  font-weight: lighter;
}

http://liveweave.com/XjrwY5


Как видим, код CSS получился довольно простым, но длинные имена тегов весьма перегружают HTML. Кроме того, имена элементов отлично подходят для указания имени блока, но совершенно не позволяют добавлять элементу модификаторов (например, чтобы как-то выделить завершённые или важные задачи). Но самая главная проблема кастомизированных имён элементов в том, что иногда имя элемента должно быть строго определённым. Например, элемент video для вставки видео на страницу, или элемент a для создания гиперссылки.


Данный способ стилизации почти не используется ввиду упомянутых ограничений. Лишь новички и энтузиасты от web components практикуют эту технику.


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


<div id="my-task-list">
  <a id="my-task-card" href="#task=1">
      <div id="my-task-card-title">Write HTML</div>
      <div id="my-task-card-estimate">1 hour</div>
  </a>
  <a id="my-task-card" href="#task=2">
      <div id="my-task-card-title">Write CSS</div>
      <div id="my-task-card-estimate">2 hours</div>
  </a>
  <a id="my-task-card" href="#task=3">
      <div id="my-task-card-title">Write JS</div>
      <div id="my-task-card-estimate">10 hours</div>
  </a>
</div>

#my-task-list {
  display: flex;
  flex-direction: column;
}

#my-task-card {
  display: inline-flex;
  margin: .5rem;
  padding: .5rem;
  border: 2px solid gray;
  border-radius: .5rem;
  text-decoration: inherit;
  color: inherit;
}

#my-task-card-title {
  margin: .5rem;
  font-weight: bolder;
  flex: 1 1 auto;
}

#my-task-card-estimate {
  margin: .5rem;
  font-weight: lighter;
}

http://liveweave.com/ccrFOf


Код CSS почти не изменился — мы лишь добавили решётки перед именами. Код HTML стал гораздо легче за счёт отсутствия повторения длинных имён. Имена тегов теперь могут быть произвольными, так что мы с лёгкостью превратили карточки задач в гиперссылки, ведущие на страницы с подробностями по этим задачам. Тем не менее, у нас по прежнему нет поддержки модификаторов. И кроме того, идеологически не верно иметь на одной странице несколько элементов с одинаковым идентификатором, хотя это ничего и не ломает.


Раньше идентификаторы пользовались популярностью для стилизации, но сейчас их полностью вытеснили классы.


Классы


<div class="my-task-list">
  <a class="my-task-card my-task-card_important my-task-card_completed" href="#task=1">
      <div class="my-task-card-title">Write HTML</div>
      <div class="my-task-card-estimate">1 hour</div>
  </a>
  <a class="my-task-card my-task-card_completed" href="#task=1">
      <div class="my-task-card-title">Write CSS</div>
      <div class="my-task-card-estimate">2 hours</div>
  </a>
  <a class="my-task-card" href="#task=1">
      <div class="my-task-card-title">Write JS</div>
      <div class="my-task-card-estimate">10 hours</div>
  </a>
</div>

.my-task-list {
  display: flex;
  flex-direction: column;
}

.my-task-card {
  display: inline-flex;
  margin: .5rem;
  padding: .5rem;
  border: 2px solid gray;
  border-radius: .5rem;
  text-decoration: inherit;
  color: inherit;
}

.my-task-card_important {
  border-color: red;
}

.my-task-card_completed {
  opacity: .5;
}

.my-task-card-title {
  margin: .5rem;
  font-weight: bolder;
  flex: 1 1 auto;
}

.my-task-card-estimate {
  margin: .5rem;
  font-weight: lighter;
}

http://liveweave.com/qZDyzV


Изменения как в CSS, так и в HTML — незначительные, но классов на один элемент можно навешивать куда больше одного, чем мы и воспользовались, дополнительно раскрасив важные и завершённые задачи. Правда, за счёт пространств имён, такие модификаторы довольно сильно раздувают HTML.


99% кода сейчас в вебе написано на классах, тем не менее есть решение лучше..


Атрибуты


<div my-task-list>
  <a my-task-card="important completed" href="#task=1">
      <div my-task-card-title>Write HTML</div>
      <div my-task-card-estimate>1 hour</div>
  </a>
  <a my-task-card="completed" href="#task=2">
      <div my-task-card-title>Write CSS</div>
      <div my-task-card-estimate>2 hours</div>
  </a>
  <a my-task-card href="#task=3">
      <div my-task-card-title>Write JS</div>
      <div my-task-card-estimate>10 hours</div>
  </a>
</div>

[my-task-list] {
  display: flex;
  flex-direction: column;
}

[my-task-card] {
  display: inline-flex;
  margin: .5rem;
  padding: .5rem;
  border: 2px solid gray;
  border-radius: .5rem;
  text-decoration: none;
  color: inherit;
}

[my-task-card~=important] {
  border-color: red;
}

[my-task-card~=completed] {
  opacity: .5;
}

[my-task-card-title] {
  margin: .5rem;
  font-weight: bolder;
  flex: 1 1 auto;
}

[my-task-card-estimate] {
  margin: .5rem;
  font-weight: lighter;
}

http://liveweave.com/8ddncZ


Код CSS усложнился незначительно, зато HTML, не смотря на поддержку модификаторов, стал куда более простым. Тут мы используем специальный селектор, который буквально означает "применить такие-то стили к элементу, у которого в таком-то атрибуте, среди разделённых пробелом слов, есть такое-то".


Не смотря на все свои преимущества, атрибуты пока не снискали популярности. Но это лишь вопрос времени — уже начинают появляться css-фреймворки, активно использующие эту технику.


Свойства


Браузеры представляют довольно ограниченный набор свойств, которые мы можем использовать в CSS через псевдоклассы. Например, мы можем добавить подсветку карточки при наведении курсора:


[my-task-card]:hover {
  border-color: steelblue;
  box-shadow: 0 0 .5rem rgba(0,0,255,.25);
  opacity: 1;
}

К сожалению набор псевдоклассов никак от нас не зависит, поэтому, если, например, нам потребуется выделить карточку текущей открытой задачи, то нам уже придётся использовать модификатор:


[my-task-card]:not([my-task-card~=current]):hover {
  border-color: steelblue;
  box-shadow: 0 0 .5rem rgba(0,0,255,.25);
  opacity: 1;
}

[my-task-card~=current] {
  background: #eee;
  border: none;
}

Получилась довольно хитрая логика и это ещё цветочки — в некоторых случаях тут получаются зубодробительные выражения. Кроме того, во время разработки мы не можем вывести карточку во всех возможных состояниях — приходится открывать страницу и возюкать мышью, проверяя, что всё работает как следует. Поэтому, если есть такая возможность, то лучше вообще отказаться от использования псевдоклассов в пользу модификаторов, контролируемых из JS:


[my-task-card~=hover] {
  border-color: steelblue;
  box-shadow: 0 0 .5rem rgba(0,0,255,.25);
  opacity: 1;
}

[my-task-card~=current] {
  background: #eee;
  border: none;
}

<div my-task-list>
  <a my-task-card="important completed" href="#task=1">
      <div my-task-card-title>Write HTML</div>
      <div my-task-card-estimate>1 hour</div>
  </a>
  <a my-task-card="completed" href="#task=2">
      <div my-task-card-title>Write CSS</div>
      <div my-task-card-estimate>2 hours</div>
  </a>
  <a my-task-card="current" href="#task=3">
      <div my-task-card-title>Write JS</div>
      <div my-task-card-estimate>10 hours</div>
  </a>
</div>

<div my-task-list>
  <a my-task-card="hover important completed" href="#task=1">
      <div my-task-card-title>Write HTML</div>
      <div my-task-card-estimate>1 hour</div>
  </a>
  <a my-task-card="hover completed" href="#task=2">
      <div my-task-card-title>Write CSS</div>
      <div my-task-card-estimate>2 hours</div>
  </a>
</div>

http://liveweave.com/1GJrUM


Тут мы вывели карточки задач во всех возможных состояниях, так что одним взглядом можем легко окинуть их все. Как написать JS, который будет менять модификаторы по необходимой логике — вопрос отдельный и во многом зависит от предпочитаемого вами фреймворка.


На этом наш небольшой обзор техник стилизации подходит к концу. Расскажите в комментариях, каких принципов вы придерживаетесь при вёрстке и как бы заверстали описанный в статье пример.

Поделиться с друзьями
-->

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


  1. webmasterx
    16.08.2016 08:25
    +4

    Неделя «Я бегло проглядел книжку/сайт по ХХХ, и понял все неправильно» объявили окрытой?

      <a id="my-task-card" href="#task=2">
          <div id="my-task-card-title">Write CSS</div>
      </a>
      <a id="my-task-card" href="#task=3">
          <div id="my-task-card-title">Write JS</div>
      </a>
    



    1. kiaplayer
      16.08.2016 08:36

      Я правильно понял, что все представленные способы, кроме использования классов, порождают невалидный html?


      1. webmasterx
        16.08.2016 08:39

        конечно


      1. youlose
        16.08.2016 08:44
        +1

        Можно data- добавлять к аттрибутам, будет валиднее. Интересна производительность такого рода селекторов, как думаете быстрее они или медленнее классов?


        1. webmasterx
          16.08.2016 08:50

          а зачем это анализировать, если у них предназначение другое?


          1. inoyakaigor
            16.08.2016 11:06
            +1

            Затем, что если они по производительности на уровне с обычными селекторами, то можно смело использовать такой способ при необходимости, а если медленнее, то об этом стоит помнить когда настанет момент оптимизации скорости отрисовки страницы.


            1. Sykoku
              17.08.2016 12:45

              Насколько я помню, для обработки «data» или «class» придется использовать стороннюю библиотеку (если лень писать свою обработку). Например, jQuery. Т.е быстрее браузерного поиска по «id» точно не получится. Т.к. сначала получим список всех элементов по «getElementsByTagName», а затем будем искать нужный.


              1. virtusIV
                17.08.2016 15:18

                Ваша память вас подводит!


            1. vintage
              22.08.2016 00:13
              +1

              http://frontender.info/css-performance-revisited-selectors-bloat-expensive-styles/


              Разница чуть менее, чем никакая.


  1. ARad
    16.08.2016 09:11
    +1

    Какая производительность у селекторов по классам и селекторов по атрибутам? Есть разница?


    1. ishashko
      16.08.2016 09:24

      Производительность у селекторов по классам лучше, чем у селекторов по атрибутам. Я видел пару раз сравнения на stackoverflow


    1. Mischuk
      17.08.2016 09:35

      Производительность по классам и по атрибутам примерно равна, но по классам быстрые на 10-20 мс, и то при условии, что сравнивается один класс и один атрибут.
      В реалиях же селекция по классам часто не ограничивается одним классом, как и в случае с атрибутами, и в таком случае производительность по атрибутам будет более заметно проседать в скорости. Особенно учитывая современные объемы стилей, то такой подход будут виден абсолютно сразу.


  1. de_arnst
    16.08.2016 09:16
    +1

    Очень все это странно, имхо

    Можно использовать бэм или не использовать его. Стили изолированно можно писать как угодно. JS к классам с префиксом js- или, в крайнем случае, к дата атрибутам.

    Привязка к именам тегов — это не гибко, в случае статьи — невалидно.
    Привязка к id — ограничение на использование один раз этого блока на странице


    1. vintage
      22.08.2016 00:27

      На самом деле никакого ограничения нет — вам наврали.


      1. de_arnst
        22.08.2016 10:57

        Я не правильно выразился. Ограничение связано не со стилями, а с использованием блока, если на него завязан js


        1. vintage
          22.08.2016 11:42

          Тут тоже нет никаких ограничений.


          1. de_arnst
            22.08.2016 11:58

            в моем случае ограничение ie7


            1. vintage
              22.08.2016 12:28

              То есть используете jQuery и вам надо писать не $('#my-task-card'), а $('[id=my-task-card]').


              1. de_arnst
                22.08.2016 12:53

                Не использую jQuery, но это все не важно, всегда можно закостылить и вот это все.

                id — должен быть уникальным по своей логике, зачем ее ломать? Ради каких целей?


                1. vintage
                  22.08.2016 18:27
                  -1

                  В IE7 реализация поиска по классу куда более костыльна, чем поиска по атрибуту.


                  ID вообще не нужен. Нет ничего уникального в этом мире. :-)


  1. Bellicus
    16.08.2016 09:42
    +5

    Я прошу прощения, а о чем статья то?


    1. Firefoxic
      17.08.2016 14:03
      +1

      Ни о чём. Практическая польза нулевая. Спасибо автору за потерянные несколько минут, которые потрачены на поиски смысла статьи.


  1. T-362
    16.08.2016 12:57

    А почему «my-task-card my-task-card_important my-task-card_completed» вместо более простого «my-task-card important completed»? При втором способе еще легче писать стили, особенно используя less.


    1. Yozi
      17.08.2016 09:36

      На вскидку:

      Есть 2 набора свойств
      1) .foo и .foo-mod
      2 ).bar и bar-mod

      Теперь появляется элемент совмещающий foo и bar.


      Конфликт получается, если нужен только «foo foo-mod bar», но никак не «foo foo-mod bar bar-mod»


      1. T-362
        17.08.2016 12:18

        Ну в таком случае да. Хотя можно развить холиворчик на архитектурную тему, мол стили должны быть не циклическим деревом.
        А еще можно использовать костыль .bar.bar-mod:not(.foo-mod), или как-то так.


        1. Yozi
          17.08.2016 13:15

          Зачастую невозможно предсказать будущее проекта, сейчас цикличности нет, а потом мы сливаем два проекта и привет, конфликты. Такая поддержка отсутствия цикличности требует нехилых экстрасенсорных способностей в предсказании будущего, мне такой уровень не по силам, и я предпочту длинные изолированные модификаторы.

          Вариант с not очень уж костыльный, и совсем не расширяемый: добавление новых стилей не должно требовать модификации уже существующих, а not потребует дописывать новые стили в исключения каждый раз.

          ====
          Я не очень хорош в объяснениях, тут достаточно неплохо описано мотивы длинного именования https://ru.bem.info/methodology/faq/#Зачем-писать-имя-блока-в-именах-модификаторов-и-элементов


          1. T-362
            17.08.2016 13:50

            Разумно, но опять же в случае слияния двух проектов можно подчистить стили и в случае очень частого совместного использования сделать общую сущность —
            .foo, .foobar {}
            .bar, .foobar {}
            .foobar {}
            .bar.bar-mod, .foobar.bar-mod {}
            Если использовать что-то в роде less то будет еще легче с переменными —
            .foobar { background-color: @foo-color; border: @bar-border }
            Ну а костыль not() на то и костыль что его можно использовать раз-другой и не поощрять такое поведение, если они начинают накапливаться — показатель, нужно уже перетасовать структуру классов.

            В общем ИМХО надо по минимуму плодить длинные сущности (как в примере в статье) и использовать стандартизированные классы для «состояния».


            1. Yozi
              17.08.2016 14:12

              «Подчистка» стилей, требует ручного вмешательства. Для очень больших проектов это становится существенной задачей (переезд хуже пожара). Это одна из техник изоляции проблем.

              .foobar опять требует от меня работы руками для написания .foobar.mod, а идея как раз в том, чтобы сочетать стили, а не писать их заново.

              Это похоже на «Наследование VS Композиция» из ООП, вместо общего предка ".mod" предлагается реализовывать trait «mod» для каждой необходимой сущности


  1. wanick
    17.08.2016 13:09

    У меня подход такой: для стилизации использую классы, иногда бывает удобнее использовать атрибуты, но никогда начинающийся на «data-», ID — практически не использую. Если нужно пометить тег для привязки к нему JS события, тут использую классы с префикс «js-», ни какие стили к нему не привязываю, для передачи данных в JS — атрибут «data-».


  1. Nekronavt
    17.08.2016 14:01

    хотя это ничего и не ломает.

    Ломает. JS у вас корректно работать не будет дальше первого айдишника.


    1. vintage
      21.08.2016 16:09

      document.querySelectorAll('#my-task-card')

      Всё замечательно работает.