БЭМантика: пишите осмысленные стили без повторов

Перевод статьи BEMantic: DRY Like You Mean It с сайта https://medium.com, опубликовано на css-live.ru с разрешения автора — Мэтта Стоу.

Недавняя статья Тима Бакстера на A List Apart, «Осмысленный CSS: описывайте стилями свою логику», подбросила дровишек в костер тому мнению, будто фронтендеры, предпочитающие объектно-ориентированный подход к CSS (БЭМ или что-то вроде), как-то напрочь забывают о семантической разметке и доступности.

Лично я считаю, что так думать просто обидно. Не понимаю, почему БЭМ и семантику HTML считают взаимоисключающими. Я тешу себя надеждой, что я хороший разработчик: я горжусь как своим HTML (включая семантику и ARIA-атрибуты), так и своим CSS, где вовсю используются Sass и БЭМ.

Я уже давным-давно занимаюсь фронтенд-разработкой и остановился на своем теперешнем подходе не потому, что я «один из зомбированной толпы» или «люблю создавать себе лишние трудности», но потому, что он помогает мне писать лучшие, легче поддерживаемые и более производительные сайты и веб-приложения. Я выбрал эти технологии и приемы не потому, что это считалось крутым или что так сказал Гарри, а потому, что изучал, оценивал и пробовал их в деле, постепенно осознавая, как сильно они выручают меня в реальных задачах из моего личного опыта, и что без них я бы запросто мог свихнуться от трудностей фронтенда.

Так что же дали мне Sass и БЭМ при разработке веб-интерфейсов, и почему?

  • Постоянство и предсказуемость
  • Специфичность
  • Переносимость и устойчивость
  • Производительность
  • Поддерживаемость и другие плюсы

Пройдемся по каждому пункту.

Постоянство и предсказуемость

Главный аргумент противников «классянки» в том, что нужно привязывать оформление к HTML-тегам и атрибутам. Простите, но, по-моему, всех тегов и атрибутов не хватит, чтобы охватить все мыслимые объекты, компоненты и разновидности таковых. Что бывает, когда у вас заканчиваются теги и атрибуты, чтобы писать стили для них? Используете классы? Вот именно, так что теперь ваш код — беспорядочная мешанина из разных типов селекторов. Читать и понимать его труднее. Если же всё оформляется с помощью классов, освоить такое гораздо проще.

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

Поднимите руки, если помните наизусть специфичность button, [type="button"] и #main. А как насчет ul > li > a? Чем можно перебить стиль в ul > li > a для третьей разновидности такой ссылки? Такой код быстро превращается в кашу. А теперь поднимите руки, если знаете специфичность оформления каждого элемента одним классом. Этого и знать-то не нужно, ведь специфичность одна и та же. Не надо жонглировать цифрами в уме, а порядок в коде решает, что чем переопределяется. Единственные места, где нужны добавочные селекторы — псевдоклассы вроде :focus, потому что им нет замены, и дописывание классов состояния, вроде .foo.is-selected, чтобы их специфичность гарантированно была выше.

Переносимость и устойчивость

В ваших руках

В своей статье Тим предлагает нам оформлять текстовые <input>ы с помощью селекторов типа [type=”text”]. Я насчитал в HTML5 как минимум 6 «текстообразных» <input>ов, плюс еще <textarea>. У себя в приложении я хочу оформить их все в одном стиле, так что, следуя подходу «семантического оформления», я был бы вынужден написать тяжеленный селектор наподобие этого:

[type="email"], [type="number"], [type="password"], [type="search"], [type="text"], [type="tel"], textarea { }

А с осмысленным классом CSS будет таким:

.o-text-input { }

что почти впятеро короче! И на будущее такой вариант надежнее, потому что, если когда-нибудь в HTML добавят новый тип поля вроде текстового, вам не придется менять CSS для его оформления — просто добавьте класс этому <input>у.

Кроме того, не знаю, как вы, а я вот то и дело забываю свои сохраненные пароли и меняю через отладчик тип у поля с паролем на text, чтоб подсмотреть их. Сайтов, на которых внешний вид поля при этом меняется до неузнаваемости, мне попадалось несметное число. Очень уж непрочный это способ оформления.

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

Например, среднестатистический зрячий пользователь знать не знает, что такое <fieldset>, но знает, как выглядит в вашем приложении группировка полей. И он наверняка захочет, чтобы какой-то контент, похожий на группу полей, который вы показываете в промежутке между реальными <fieldset>ами, отображался в том же визуальном стиле.

Можно добиться этого такими стилями:

fieldset, .field-group { }
legend, .field-group > h2:first-child, .field-group > h3:first-child, .field-group > h4:first-child, .field-group > h5:first-child, .field-group > h6:first-child { }

а можно просто создать 2 класса:

.field-group { }
.field-group__legend { }

где первый можно будет применять к <fieldset>ам, <section>ам и <div>ам, а второй — к <legend>ам или заголовкам любого уровня, смотря что лучше подходит семантически в данном контексте.

Тим также предлагает оформлять компонент доступных вкладок с помощью подходящих ARIA-ролей, например, [role="tab"] and [role="tabpanel"]. Это приводит к сложностям, когда в приложении несколько разных типов вкладок, поскольку приходится уточнять их с помощью необязательных оберток, и велик риск наступить на грабли со специфичностью. А чтобы сильнее подчеркнуть глубину проблемы, если в приложении используются еще и доступные аккордеоны, то им нужны совсем другие стили и разметка, чем вкладкам — но ARIA-роли и состояния в них используются в точности те же самые, так что различить их по одной лишь семантике невозможно.

Как и следовало ожидать, это легко обойти с помощью БЭМ-классов:

.tab-trigger {
    // Стили для ссылки вкладки
    &.is-selected {
        // Стили для ссылки выбранной вкладки
    }
}
.accordion-trigger {
    // Стили для ссылки аккордеона. Это может быть и заголовок, если это цельная единица сворачиваемого контента
    &.is-selected {
        // Стили для ссылки выбранной секции аккордеона
    }
}

Конечно, классы — не замена ARIA-состояниям, вроде aria-selected и aria-expanded: они вам всё равно понадобятся, но, как я отметил ранее, при оформлении с помощью одних только классов код становится более постоянным и предсказуемым.

Не в вашей власти

Но бывают и такие обстоятельства, при которых разметка вам неподвластна. Хотя вы вправе считать то, что CMS или фреймворк принудительно меняют разметку, злостным багом этой CMS или фреймворка, я полагаю, что это ваш CSS слишком хрупок, раз не выдерживает даже незначительных изменений. Стили не должны ломаться оттого лишь, что React-компоненту понадобился элемент-обертка, или что Angular нуждается в тегах для перебора через ng-repeat.

Наивно полагать, будто отображаемая разметка навсегда останется в своем изначально задуманном, первозданном виде. Без компромиссов не обойтись, иначе весь код не будет переносимым. Когда другому разработчику понадобится взять ваш проект в свои руки и перенести его на другую платформу или фреймворк, ему придется также исправлять семантические стили, что сейчас по какой-то причине толком не действуют. Такое вполне может произойти и внутри вашей команды. Я собираю всё статически (с помощью gulp-twig), так что могу целиком сосредоточиться на создании великолепного интерфейса. Мой коллега часто преобразует его в компоненты React или директивы Angular, смотря что нужно для проекта. За время использования БЭМа у меня еще ни разу не бывало, чтобы страницы и компоненты после преобразования оказались оформлены неправильно, тогда как до него такое происходило систематически, поскольку моя разметка была слишком идеалистичной и не вписывалась в реальность.

Производительность

Я сам ни разу не замерял производительность селекторов, но упорно ходят байки, что она может существенно влиять на отрисовку приложения. На MDN есть большое руководство по написанию эффективного CSS, которое по сути советует не использовать семантических и перегруженных селекторов. Стив Саудерс написал книгу «Еще более быстрые сайты», в которой есть глава про упрощение CSS-селекторов, и Гарри Робертс тоже раскрыл эту тему.

Совсем недавно Пол Айриш написал комментарий о производительности селекторов, а Марси Саттон отмечает, что IE11 нещадно тормозил, когда флексбоксы применялись с селектором атрибута, а не с классами.

Так что, даже если вы сами не сталкивались с проблемами производительности (а вы вообще проверяли свою работу в профайлере?), зачем зря рисковать, отказываясь от использования классов?

Поддерживаемость и другие плюсы

И для CSS, и для Sass правила хорошего тона советуют разбивать CSS/Sass-исходник на несколько файлов — в идеале по своему файлу на каждый блок или компонент в БЭМ. И новичкам, и «старожилам» кода это здорово облегчает поиск стилей для конкретного компонента в разметке.

Если в вашей организации есть свой фреймворк-заготовка, который используется во всех проектах, то с отдельными файлами для компонентов еще и намного проще выборочно подключить лишь те, что используются в конкретном проекте — уменьшив тем самым общий вес стилей. А еще отдельные файлы упрощают процесс генерации библиотек типовых решений на их базе.

Кроме того, сколько раз бывало, что вы добавляли в проект сторонний виджет, а ваши стили влияли на него  через каскад и портили его вид? В идеале, авторы этого виджета должны сами как следует предусмотреть защиту от этого, но без глобально видимого CSS c семантическими селекторами риск возможных конфликтов намного меньше.

И наконец, если для вас доступность — не пустой звук, вы должны допускать и такую возможность, что конечный пользователь может переопределить ваши стили своими, пользовательскими. Если всё разбито на компоненты, переопределить конкретный элемент намного проще. Хотите спрятать текущий счет на спортивном сайте, чтобы было интереснее следить за игрой? С селектором типа .score--in-progress это будет гораздо легче для пользователя, чем с #scoreboard > li > b + span, который к тому же скорее изменится с развитием проекта.

Заключение

Насколько я могу судить, изрядная часть недовольства новыми технологиями «растет» из поверхностного впечатления: противный, «тошнотворный» синтаксис БЭМ, или засорение разметки атрибутами data-reactid, которые, увы, мешают людям распробовать их как следует. Да, перемены нелегки и требуют времени, но я считаю, что ваша обязанность перед самими собой и коллегами — хотя бы попытаться. Наша отрасль постоянно меняется, учится чему-то новому и приспосабливается к нему, и именно по этой причине работать в ней так здорово.

Не спорю, вам могли попадаться какие-то удручающе неадекватные, без намека на доступность, сайты с использованием БЭМ, увы, среди разработчиков всегда будут и халтурщики, и пофигисты. Но всё же глупо отмахиваться от плодов труда множества разработчиков и от очевидных преимуществ популярных методологий из-за ошибок совсем других разработчиков.

Как бы то ни было, я ведь и сам не сразу осознал ценность Sass и БЭМ.

Копия этой статьи была опубликована на Codepen, ради лучшего форматирования кода.

P.S. Это тоже может быть интересно:

9 Комментарии

  1. AntonMMF

    Все текстовые инпуты легко оформляются через input {}, никакого БЭМ и прочего там не надо

    Это влияет и на нетекстовые инпуты, но:
    1) Чекбоксы и радио уже давно скрываются на сайтах и делаются кастомными;
    2) Файловый инпут тоже давно делается давно кастомным;
    3) Кнопка отправки, свёрстанная через button, более управляема в CSS.

    1. SelenIT (Автор записи)

      Есть еще экзотика типа [type="range"], хотя ее, наверное, тем более заменят чем-то кастомным… Но вообще очень дельное замечание!

  2. Tony

    На самом деле, я ждал подобную статью в ответ на огорчившую меня от Тима Бакстера. И полностью согласен с Мэттом Стоу!

  3. Алексей

    Один из тех вопросов, ответ на который, пожалуй, интересно найти самому…

  4. Мимо проходил

    С селектором типа .score — in-progress это будет гораздо легче для пользователя, чем с #scoreboard > li > b + span, который к тому же скорее изменится с развитием проекта.

    Как будто .score — in-progress не меняется.
    Я вообще с трудом могу менять стили главной яндекса, свёрстанной по БЭМу по самый небалуй, потому что там постоянно меняются эти БЭМ классы.

    1. SelenIT (Автор записи)

      Резонное замечание!

  5. Константин

    Интересно, а причём здесь sass?

    1. SelenIT (Автор записи)

      Как минимум, он упоминается в одном из примеров). Ну и, видимо, воплощает собой еще одно преимущество абстрагирования CSS от разметки, в отличие от «лобового» подхода к оформлению, за который ратовал Тим Бакстер…

      1. Константин

        Так Sass или любой другой язык-надстройка – всего лишь еще один топор, которыми делается один и тот же CSS.

        Самое безопасное, что можно представить в этих надстройках в связке с БЭМ – nesting. Самое страшное – селекторы вида &—title.

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.