Лучшие практики стилизации элементов сейчас можно выразить следующими тезисами:
- Старайтесь стилизовать элементы так, чтобы их визуализация не ломалась при перемещении их в другое место. Из этого принципа следует, что стоит минимизировать использование составных селекторов (например, вида
header p a
). - Используйте пространства имён, чтобы минимизировать вероятность конфликтов правил относящихся к разным элементам. Это приводит к длинным именам, но избавляет от кучи проблем в будущем.
Итак, какие возможности для привязки стилей к элементам у нас есть сейчас? У любого элемента есть следующие характеристики:
- Имя элемента
- Идентификатор
- Набор классов
- Набор атрибутов
- Набор свойств
Давайте посмотрим, как мы можем их использовать, на примере простого списка задач, содержащего карточки с названием задачи и оценкой времени её выполнения...
Имя элемента
<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;
}
Как видим, код 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;
}
Код 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;
}
Изменения как в 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;
}
Код 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>
Тут мы вывели карточки задач во всех возможных состояниях, так что одним взглядом можем легко окинуть их все. Как написать JS, который будет менять модификаторы по необходимой логике — вопрос отдельный и во многом зависит от предпочитаемого вами фреймворка.
На этом наш небольшой обзор техник стилизации подходит к концу. Расскажите в комментариях, каких принципов вы придерживаетесь при вёрстке и как бы заверстали описанный в статье пример.
Комментарии (31)
ARad
16.08.2016 09:11+1Какая производительность у селекторов по классам и селекторов по атрибутам? Есть разница?
ishashko
16.08.2016 09:24Производительность у селекторов по классам лучше, чем у селекторов по атрибутам. Я видел пару раз сравнения на stackoverflow
Mischuk
17.08.2016 09:35Производительность по классам и по атрибутам примерно равна, но по классам быстрые на 10-20 мс, и то при условии, что сравнивается один класс и один атрибут.
В реалиях же селекция по классам часто не ограничивается одним классом, как и в случае с атрибутами, и в таком случае производительность по атрибутам будет более заметно проседать в скорости. Особенно учитывая современные объемы стилей, то такой подход будут виден абсолютно сразу.
de_arnst
16.08.2016 09:16+1Очень все это странно, имхо
Можно использовать бэм или не использовать его. Стили изолированно можно писать как угодно. JS к классам с префиксом js- или, в крайнем случае, к дата атрибутам.
Привязка к именам тегов — это не гибко, в случае статьи — невалидно.
Привязка к id — ограничение на использование один раз этого блока на страницеvintage
22.08.2016 00:27На самом деле никакого ограничения нет — вам наврали.
de_arnst
22.08.2016 10:57Я не правильно выразился. Ограничение связано не со стилями, а с использованием блока, если на него завязан js
vintage
22.08.2016 11:42Тут тоже нет никаких ограничений.
de_arnst
22.08.2016 11:58в моем случае ограничение ie7
vintage
22.08.2016 12:28То есть используете jQuery и вам надо писать не
$('#my-task-card')
, а$('[id=my-task-card]')
.de_arnst
22.08.2016 12:53Не использую jQuery, но это все не важно, всегда можно закостылить и вот это все.
id — должен быть уникальным по своей логике, зачем ее ломать? Ради каких целей?vintage
22.08.2016 18:27-1В IE7 реализация поиска по классу куда более костыльна, чем поиска по атрибуту.
ID вообще не нужен. Нет ничего уникального в этом мире. :-)
T-362
16.08.2016 12:57А почему «my-task-card my-task-card_important my-task-card_completed» вместо более простого «my-task-card important completed»? При втором способе еще легче писать стили, особенно используя less.
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»T-362
17.08.2016 12:18Ну в таком случае да. Хотя можно развить холиворчик на архитектурную тему, мол стили должны быть не циклическим деревом.
А еще можно использовать костыль .bar.bar-mod:not(.foo-mod), или как-то так.Yozi
17.08.2016 13:15Зачастую невозможно предсказать будущее проекта, сейчас цикличности нет, а потом мы сливаем два проекта и привет, конфликты. Такая поддержка отсутствия цикличности требует нехилых экстрасенсорных способностей в предсказании будущего, мне такой уровень не по силам, и я предпочту длинные изолированные модификаторы.
Вариант с not очень уж костыльный, и совсем не расширяемый: добавление новых стилей не должно требовать модификации уже существующих, а not потребует дописывать новые стили в исключения каждый раз.
====
Я не очень хорош в объяснениях, тут достаточно неплохо описано мотивы длинного именования https://ru.bem.info/methodology/faq/#Зачем-писать-имя-блока-в-именах-модификаторов-и-элементов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() на то и костыль что его можно использовать раз-другой и не поощрять такое поведение, если они начинают накапливаться — показатель, нужно уже перетасовать структуру классов.
В общем ИМХО надо по минимуму плодить длинные сущности (как в примере в статье) и использовать стандартизированные классы для «состояния».Yozi
17.08.2016 14:12«Подчистка» стилей, требует ручного вмешательства. Для очень больших проектов это становится существенной задачей (переезд хуже пожара). Это одна из техник изоляции проблем.
.foobar опять требует от меня работы руками для написания .foobar.mod, а идея как раз в том, чтобы сочетать стили, а не писать их заново.
Это похоже на «Наследование VS Композиция» из ООП, вместо общего предка ".mod" предлагается реализовывать trait «mod» для каждой необходимой сущности
wanick
17.08.2016 13:09У меня подход такой: для стилизации использую классы, иногда бывает удобнее использовать атрибуты, но никогда начинающийся на «data-», ID — практически не использую. Если нужно пометить тег для привязки к нему JS события, тут использую классы с префикс «js-», ни какие стили к нему не привязываю, для передачи данных в JS — атрибут «data-».
webmasterx
Неделя «Я бегло проглядел книжку/сайт по ХХХ, и понял все неправильно» объявили окрытой?
kiaplayer
Я правильно понял, что все представленные способы, кроме использования классов, порождают невалидный html?
webmasterx
конечно
youlose
Можно data- добавлять к аттрибутам, будет валиднее. Интересна производительность такого рода селекторов, как думаете быстрее они или медленнее классов?
webmasterx
а зачем это анализировать, если у них предназначение другое?
inoyakaigor
Затем, что если они по производительности на уровне с обычными селекторами, то можно смело использовать такой способ при необходимости, а если медленнее, то об этом стоит помнить когда настанет момент оптимизации скорости отрисовки страницы.
Sykoku
Насколько я помню, для обработки «data» или «class» придется использовать стороннюю библиотеку (если лень писать свою обработку). Например, jQuery. Т.е быстрее браузерного поиска по «id» точно не получится. Т.к. сначала получим список всех элементов по «getElementsByTagName», а затем будем искать нужный.
virtusIV
Ваша память вас подводит!
vintage
http://frontender.info/css-performance-revisited-selectors-bloat-expensive-styles/
Разница чуть менее, чем никакая.