Как я проект на БЭМ переводил… и перевел

от автора

Связка HTML и CSS (CSS в большей степени) всегда казалась мне несколько «туманной», хуже всего поддающейся контролю и тестированию. Я придумывал для себя различные правила и пытался так или иначе стандартизировать свой подход, но не было ощущения, что «вот, это оно». Я несколько раз мельком знакомился с БЭМ (и не только), читал статьи на эту тему, но дальше чтения дело не заходило. Но чем дальше, тем сильнее было ощущение необходимости в наличии определенной строгой методологии. В конце концов, я решил попробовать внедрить БЭМ на одном из своих проектов, где без этого, на мой взгляд, было не обойтись. Речь идет о CMS, упрощенную страничку бекенда которой я приведу в качестве примера верстки:

Сразу хочу заметить, что БЭМ — это далеко не методология на все случаи жизни, и вопрос о необходимости ее применения в том или ином проекте следует рассматривать в частном порядке (в том числе исходя из того, нравится она вам или нет). Также, в силу того, что я не использовал предлагаемую специфическую файловую структуру или генерацию HTML, о них говорить не будем (позднее я все-таки разделил CSS-файл на отдельные части, соответствующие блокам, но этим решил пока ограничиться). Также, уже достаточно много (например, вот и вот) написано о достоинствах и недостатках этого подхода в целом, поэтому говорить об этом тоже не будем, я просто поделюсь своим опытом и размышлениями на эту тему, предполагая, что с сутью вы уже знакомы.

Приступаем

Итак, в моем случае CMS — большой и модульный проект с открытым исходным кодом, в котором каждый модуль может иметь собственные куски бекенда, используя общие и добавляя к ним специфические элементы интерфейса. В идеале, модули должны разрабатываться и другими разработчиками. Думаю, это как раз такой проект, где применение БЭМ (или другой серьезной методологии) является не просто уместным, а необходимым. Особенно это важно, т. к. многие из веб-разработчиков считают CSS побочной, недостойной внимания и глубокого изучения технологией, пытаясь добиться необходимого результата путем копирования готовых кусков разметки и стилей, концентрируясь на том, что им больше нравится, получая в итоге неподдерживаемые таблицы стилей ужасающего качества.

На первых порах проще всего было вообще не трогать исходный код моего проекта, т. к. он достаточно крупный. Вместо этого я создал простой HTML-документ (/index.html) и таблицу стилей (/css/style.css) для него, положил все это на рабочий стол для удобства и решил для начала сверстать несколько фрагментов с картинки выше, используя для этого Notepad++ и браузер. (В результате я хотел получить страницу, содержащую вообще все необходимые мне составные части, и уже затем, в случае успеха, перенести это в свой проект. Упрощенный результат доступен для изучения по ссылке в конце статьи; ссылку на то, как все вышло в реальности, тоже можно глянуть там.)

Кнопки

Я решил начать не со структуры, а с маленького блока кнопки — button. Кнопки у меня бывают 3-х типов: позитивное действие, негативное действие и нейтральное действие. Отличаются они лишь цветом, поэтому эти отличия я описал в виде булевых модификаторов, соответственно, button—positive, button—negative и button—neutral (я выбрал альтернативный синтаксис для модификаторов и вместо одного символа подчеркивания использую два дефиса — для меня это выглядит значительно нагляднее).

В результате в HTML кнопка описывается таким образом:

<button class="button button--positive" type="button">Text</button> 

Также допусти́м и такой вариант (одной из особенностей БЭМ является, в идеале, независимость внешнего вида от используемого тега, хотя я считаю достаточным исходить из того, к каким тегам класс может применяться, и не пытаться предусмотреть все, раздувая таблицу стилей лишними правилами):

<a class="button button--neutral" href="#">Cancel</a> 

Выглядит вполне читаемо и понятно. Посмотрим теперь на CSS:

.button {   border: none;   cursor: pointer;   font: normal 15px 'PT Sans', sans-serif;   line-height: 20px;   display: inline-block;   padding: 5px 10px; } 

Кнопка описана очень просто. Хотя я и встречал рекомендации сбрасывать значения всех правил внутри своих классов (чтобы на них не влияло окружение), но, как по мне, это уже слишком, и требуется лишь тогда, когда есть реальный шанс, что вы будете повторно использовать ваш блок в каком-то другом проекте, где стиль верстки отличается от вашего (например, вы разрабатываете какой-то виджет, который нельзя вставить как iframe в целевую страницу). Класс блока button, как и требует БЭМ, никаким образом не специфицирует свои размеры или внешние отступы.

Идем далее:

.button--positive {   background-color: #87b919;   color: #fff; }  .button--positive:hover {   background-color: #a0d71e;   color: #fff; }  .button--negative {   background-color: #ff4100;   color: #fff; }  .button--negative:hover {   background-color: #ff7346;   color: #fff; }  .button--neutral {   background-color: #f0f0f0; }  .button--neutral:hover {   background-color: #f5f5f5; } 

Эти классы определяют модификаторы для различных типов кнопок (в зависимости от действия) и их состояния при наведении на них курсора мыши.

Посмотрим на наши кнопки вживую:

По-моему, хорошо.

Группы кнопок

В моем проекте я практически нигде не использую кнопки сами по себе, они почти всегда сгруппированы в группы (например, «Сохранить» и «Отмена» в форме). В каждой группе кнопки должны быть расположены горизонтально, на расстоянии ровно в 1 пиксель друг от друга. Чтобы не испытывать затруднений с выдерживанием этого расстояния (в случае с inline- или inline-block-элементами оно зависело бы от форматирования HTML, а именно, от наличия пробела между тегами), проще всего добавить кнопкам правило float: left, но только тогда, когда кнопка является элементом группы кнопок (т. е. само собой было бы неверно добавлять это правило непосредственно блоку button).

Итак, опишем блок группы кнопок buttons с единственным элементом buttons__button, представляющим кнопку, входящую в группу. Тогда HTML группы кнопок будет выглядеть вот так:

<div class="buttons">   <button class="buttons__button button button--positive" type="button">Send</button>   <a class="buttons__button button button--neutral" href="#">Cancel</a> </div> 

Рассмотрим CSS:

.buttons { } 

Класс блока buttons пуст.

.buttons::after {   content: '';   display: block;   clear: both; } 

Поскольку к кнопкам внутри группы будет применяться правило float: left (причину я описал выше), я отменяю обтекание таким образом. Кстати, этот способ закрытия потока обтекания float нравится мне больше всего, хотя в устаревших браузерах он и не будет работать (чаще всего ориентироваться на них нет необходимости). В любом случае, не в этом суть.

.buttons__button {   float: left;   margin-right: 1px; } 

Здесь мы непосредственно описываем элемент-кнопку, входящую в группу, с отступом в одну точку справа.

.buttons__button:last-child {   margin: 0; } 

Последняя кнопка в группе не должна иметь отступа справа, используем псевдокласс :last-child для этого. Я использовал именно псевдокласс, а не модификатор, т. к. все без исключения кнопки в группах, если они расположены последними, не должны иметь этот отступ справа. Считаю использование модификаторов в таком случае излишним.

На мой взгляд, получается достаточно здорово. Сами по себе блоки никак не позиционируют себя, не описывают внешних отступов. Но когда мы помещаем блок в другой блок, он как бы одновременно становится элементом своего блока и именно класс элемента позволяет дополнительно специфицировать все необходимые правила его расположения, если они необходимы. Кстати, я всегда располагаю классы элемента первыми, затем следуют классы модификаторов элемента, а уже затем — классы блока и его модификаторов. Это очень упрощает чтение HTML, т. к. если классов много, то сразу понятнее, что во что входит. Еще момент (на всякий случай). Порядок применения CSS-классов определяется порядком их следования в CSS-файле (а не в атрибуте class, как могло бы показаться), поэтому объявлять классы следует начинать с самых простых блоков, и в самом конце размещать блоки, отвечающие за общую структуру страницы.

Вот как наша группа кнопок выглядит в браузере:

На этом с кнопками мы почти покончили, идем дальше.

Текстовые поля и текстовые области

Далее я решил разобраться с другими элементами управления. Аналогичным образом описал блоки текстового поля text-box и текстовой области text-area (текстовую область рассматривать не будем, т. к. блоки практически идентичны — в исходниках примера можно посмотреть). Далее приведен HTML блока текстового поля. Дополнительно добавлен модификатор text-box—required, означающий, что поле является обязательным к заполнению (он добавляет красную полоску справа от поля):

<input class="text-box text-box--required" type="text" /> 

Соответствующие CSS-классы выглядят так:

.text-box {   background-color: #f0f0f0;   border: none;   font: normal 15px 'PT Sans', sans-serif;   line-height: 20px;   outline: none;   padding: 5px 10px;   resize: none; }  .text-box:hover {   background-color: #f5f5f5; }  .text-box:focus {   background-color: #f5f5f5; }  .text-box--required {   border-right: 5px solid #ff4100; } 

Ничего особенного здесь нет, за исключением, повторюсь, последнего модификатора text-box—required. У текстовой области тоже есть такой, но называется он text-area—required.

Выглядит наше текстовое поле следующим образом:

Поля форм

Как и в случае с кнопками, текстовые поля и области редко применяются сами по себе в моем проекте. Чаще всего они используются в составе форм в виде полей форм (совокупность заголовка и текстового поля, например). Т. е. формы собираются из небольших готовых кусков, а не из отдельных элементов управления. Поэтому я решил добавить блок field, и описать, как ведут себя заголовки и текстовые поля и области внутри поля формы с помощью элементов field__label, field__text-box и field__text-area. В итоге HTML поля формы с текстовой областью выглядит так:

<div class="field">   <label class="field__label label">Body</label>   <textarea class="field__text-area text-area"></textarea> </div> 

Все просто. Еще раз обратите внимание на порядок следования классов. Сперва, например, следует field__label, а label — после него, т. к. тег label является в первую очередь элементом field__label своего блока field, а уже потом независимым блоком label. Такое единообразие очень помогает. Рассмотрим CSS:

.field { } 

Этот класс пуст. При отображении полей форм непосредственно в формах нам потребуется, чтобы между ними были вертикальные отступы, но мы опишем это в соответствующем элементе form__field блока form далее.

.field__label {   display: block;   margin-bottom: 1px; } 

Заголовки внутри блока field будут выводиться с новой строки и иметь отступ в один пиксель снизу.

.field__text-box {   width: 430px; }  .field__text-area {   width: 430px;   height: 190px; } 

Этими двумя классами мы задаем размеры для текстовых поля и области, когда они являются элементами поля формы. Результат всего этого следующий:

Также часть полей форм у меня являются локализируемыми (мультиязычными). Им необходим дополнительный визуальный маркер для указания языка, к которому относятся входящие в них текстовые поля или области. В HTML поле формы с набор локализируемых текстовых полей выглядит следующим образом:

<div class="field">   <label class="field__label label">Subject</label>   <div class="field__culture">     <div class="field__culture-flag">en</div>   </div>   <input class="field__text-box field__text-box--multilingual text-box text-box--required" type="text" />   <div class="field__multilingual-separator"></div>   <div class="field__culture">     <div class="field__culture-flag">ru</div>   </div>   <input class="field__text-box field__text-box--multilingual text-box text-box--required" type="text" /> </div> 

Обратите внимание на набор классов текстового поля, их четыре. Давайте еще раз по ним пройдемся. Класс field__text-box определяет размеры текстового поля внутри поля формы, field__text-box—multilingual добавляет небольшой дополнительный отступ справа, чтобы символы при наборе не залезали под маркер языка, который отображается поверх текстового поля. Класс text-box определяет основные параметры текстового поля, а text-box—required добавляет красную полоску справа от поля.

Новые CSS-классы:

.field__culture {   position: relative;   left: 450px;   width: 0;   z-index: 10; }  .field__culture-flag {   background-color: #323232;   color: #fff;   cursor: default;   font-size: 8px;   line-height: 16px;   text-align: center;   text-transform: uppercase;   position: absolute;   left: -23px;   top: 7px;   width: 16px;   height: 16px; }  .field__multilingual-separator {   height: 1px; } 

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

Формы

Теперь, рассмотрим блок формы form. Формы состоят из полей форм и групп кнопок, которые у нас уже описаны, но добавляют к ним вертикальные отступы с помощью классов элементов form__field и form__buttons. Вот так выглядит упрощенный HTML блока form:

<form class="form">   <div class="form__field field">     <label class="field__label label">Body</label>     <textarea class="field__text-area text-area"></textarea>   </div>   <div class="form__buttons buttons">     <button class="buttons__button button button--positive" type="button">Send</button>     <a class="buttons__button button button--neutral" href="#">Cancel</a>   </div> </form> 

А вот так выглядит его CSS:

.form { }  .form__field {   margin-top: 10px; }  .form__buttons {   margin-top: 20px; } 

Как видим, все достаточно очевидно. При необходимости, мы можем вставить, например, группу кнопок в какую-нибудь панель управления на нашем сайте, но если мы говорим о форме, то, снабдив группу кнопок дополнительным классом form__buttons, мы получим необходимый отступ сверху.

В браузере форма целиком выглядит так:

Таблицы

Теперь займемся немного более сложным элементом — таблицей. Думаю, все знают, что таблицы следует верстать таблицами (т. к. это семантически верно и имеет хорошую поддержку браузеров), но, в случае с адаптивной версткой, иногда удобнее все-таки это делать, используя теги общего назначения div со стилями, вроде display: table. В таком случае, на мобильных устройствах горизонтальную таблицу легко превратить в вертикальный список, всячески манипулируя отображаемыми данными (что-то можно скрыть, а что-то — объединить). Как бы там ни было, для реализации таблиц в своем проекте я решил использовать table, но, отчасти в качестве эксперимента, перед этим сверстал ее с использованием div. Прелесть независимости БЭМ от тегов в том, что, заменив затем теги div на table, tr и td, мне ничего не пришлось изменять в своем CSS-файле, таблица выглядела идентично. Я привел оба варианта для сравнения.

Стандартная таблица в HTML выглядит так:

<table class="table">   <tr class="table__row">     <th class="table__cell table__cell--header">Cell</th>     <th class="table__cell table__cell--header">Cell</th>     <th class="table__cell table__cell--header">Cell</th>   </tr>   <tr class="table__row">     <td class="table__cell">Cell</td>     <td class="table__cell">Cell</td>     <td class="table__cell">Cell</td>   </tr> </table> 

Как видим, каждому тегу дан класс. Может показаться непривычным, зато это дает возможным безболезненно поменять table, tr и td на div и не визуально не заметить различия.

CSS таблицы:

.table {   border-collapse: collapse;   display: table;   width: 100%; }  .table__row {   display: table-row; }  .table__cell {   font-weight: normal;   text-align: left;   vertical-align: top;   display: table-cell;   padding: 5px 10px; }  .table__row:hover .table__cell {   background: #ffff96; }  .table__cell--header {   background: #f0f0f0; }  .table__row:hover .table__cell--header {   background: #f0f0f0; } 

Как видим, для самого блока, как и для его элементов, установлены правила display: table, display: table-row и display: table-cell. Благодаря этому блок становится относительно независимым от тегов. По сути, повторюсь, не думаю, что есть смысл в этих правилах, если вы уверены, что таблица будет всегда сверстана именно стандартными табличными тегами.

Ну и наконец, посмотрим на результат вживую:

Меню

Переходим к завершающему этапу. Меню представлены блоком menu. Каждое меню может содержать несколько групп элементов меню (элемент menu__group), каждая из которых, в свою очередь, может содержать один заголовок группы элементов меню (элемент menu__group-title) и несколько элементов меню (элемент menu__item). Вот соответствующий HTML:

<div class="menu">   <div class="menu__group">     <div class="menu__group-title sub-title">       Group title 1     </div>     <a class="menu__item" href="#">Menu item 1</a>     <a class="menu__item" href="#">Menu item 2</a>     <a class="menu__item" href="#">Menu item 3</a>   </div> </div> 

Я думал над тем, чтобы сделать элементы menu__group и menu__item отдельными блоками, но не нашел аргументов в пользу такого решения: нигде больше они не используются, это привело бы лишь к увеличению количества классов.

Вроде бы все очевидно, но для наглядности приведу еще и CSS:

.menu { }  .menu__group { }  .menu__group-title{ }  .menu__item {   display: block;   padding: 5px 0; } 

В данном случае у меня пусты некоторые классы. Как видим, например, внешний вид заголовков групп элементов меню определяется общим блоком sub-title (я на нем не останавливался — посмотрите, пожалуйста, в исходниках). Необходимости в пустых классах нет (скорее, есть необходимость в их удалении). Я их решил оставить для наглядности нашего примера.

Меню само по себе выглядит так:

Общая структура

Напоследок, рассмотрим общую структуру страницы. Но перед этим я хотел бы коснуться еще одного момента. Дело в том, что в основном в статьях по БЭМ рекомендуют не иметь в CSS-файле правил, не относящихся к блокам. Т. е. общих правил, применимых ко всему документу (например, с селектором по тегу, а не по классу). Я решил не идти этим путем, т. к. в таком случае увеличивается количество правил, которые необходимо дублировать в каждом блоке или элементе. Я не вижу особой причины делать это в моем случае. Если задуматься, то все блоки в моем проекте описываются в рамках единого контекста, и он неизменяем, поэтому вполне допустимо, например, задать общий стиль для текста, и во всех блоках отталкиваться от него, т. к. они все-равно должны иметь обобщенный стиль.

Кроме того, мне кажется лишним назначать класс для каждого заголовка, абзаца в тексте, каждой ссылки. В таком случае, при использовании, например, WYSIWYG-редактора нам потребовалось бы добавлять эти классы вручную (или делать это автоматически при сохранении). Так или иначе, это лишнее неудобство.

Вернемся к общей структуре. Я решил представить ее одним блоком master-detail с двумя основными элементами: master-detail__master и master-detail__detail, отвечающими, соответственно, за левую-темную и правую-светлую части страницы.

В master-detail__master я добавил два меню. Одно меню не содержит никаких дополнительных классов элемента master-detail__master, т. к. нет нужды дополнять его какими-то CSS-правилами. Второе же меню является одновременно элементом master-detail__secondary-menu, что позиционирует его внизу элемента master-detail__master. Дополнительно, элементы этого второго меню «замиксованы» с элементом master-detail__secondary-menu-item, что придает им серый цвет.

Не буду приводить HTML/CSS этого блока, т. к. он слишком громоздкий, и его необходимо рассматривать в контексте остального содержимого страницы. Поэтому, предлагаю взглянуть на исходники тестового примера, ссылку на которые можно найти ниже.

Также на странице остался еще один блок — табы. Решил, что описывать их уже не имеет смысла, т. к. блок очень прост.

Ну что же, в итоге мы получим такой результат, как на первой картинке.

Выводы

Зачем я решил написать это? Когда я принялся разбираться с БЭМ у меня было много вопросов, на которые я не находил однозначных ответов. Это был некий полуфабрикат идеи. Было интересно перестроиться и взглянуть по-новому на процесс HTML-верстки, отказаться от использования каскадов и так далее. В результате я так или иначе нашел для себя решения, и мне захотелось поделиться этим опытом, чтобы постараться упростить для кого-то этот процесс «привыкания и перестроения», показав еще одну точку зрения.

Методология в целом мне понравилась. Самое важное, на мой взгляд, что она загоняет разработчика в достаточно жесткие рамки в плане структурирования, стиля именования и так далее. В итоге в большинстве случаев есть всего один ответ на вопрос «как?», что очень здорово (особенно, в крупных и командных проектах).

Стану ли я применять БЭМ на мелких и простых проектах? Пока не знаю. Хотя я и не испытывал вообще никаких лишних сложностей и не заметил лишнего «напряга» из-за увеличившегося количества классов, но все-таки следование методологии требует несколько бо́льших усилий, чем при «традиционном» подходе. Хотя, вполне возможно, это из-за недостатка опыта и сноровки.

Надеюсь, было интересно. Вживую посмотреть и потрогать можно тут, а здесь лежит реальная демка проекта, кусок упрощенной админки которого я привел в качестве примера. Там можно глянуть в реальном масштабе как это выглядит, а также, при желании, сравнить с тем, что было раньше.
ссылка на оригинал статьи https://habrahabr.ru/post/318204/


Комментарии

Добавить комментарий

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