Верстка для самых маленьких. Верстаем страницу по БЭМу
Недавно хабраюзер Mirantus написал статью «Как сверстать веб-страницу», в которой рассказывал о том, как же сверстать веб-страничку. В его статье было подробно рассмотрено, как выделить отдельные элементы из заданного шаблона, подобрать шрифты и т.п. Однако его подход к написанию, собственно, веб-страницы мне показался не очень хорошим, о чем я написал в комментариях.
В данной статье я хочу рассказать, о том, как можно сверстать «хорошо» (по крайней мере структурировано ;), а заодно рассказать и о методологии, которая может «упростить жизнь» при верстке. Структура поста будет следующей:
- BEM
- Собственно пример — как сверстать страницу
BEM
БЭМ (Блок, Элемент, Модификатор) — методология разработанная внутри Яндекса предлагает следующую концепцию (если описать 1-2 предложениями):
Любая веб-страница — набор блоков, которые состоят из элементов, причем элементом может быть другой блок (таким образом мы получаем вложенность). При необходимости мы можем модифицировать «стандартное» отображение блока\элемента, путем добавления к нему модификатора.
Очень хорошее «руководство к действию» можно найти здесь: ru.bem.info/method/definitions
Предлагаю дать определения основным элементам:
Блок — часть страницы, являющаяся логически независимой от остального наполнения. Представляет собой «строительную единицу» для сайта (на примере конструктора лего — это отдельный «кирпичек»)
Блок не отвечает за свое расположение. Он задает внутренние свойства (размеры, шрифты и т.д.)
Внутри Блок содержит Элементы. Элемент — часть блока, которая отвечает за отдельную задачу (например, это расположение внутри блока). Элемент должен входить в состав блока и не должен иметь какого-либо смысла отдельно от блока.
Пример выделения блоков\элементов
Для примера возьмем блок «прямой эфир» на сайте Хабра:
Вот так он выглядит:
Если «организовать» его по методологии БЭМ, то данная часть будет являть собой блок, состоящий из элементов:
Соответственно, данный блок состоит из 3 разных типов элементов. (Здесь стоит сделать оговорку, что возможно сверстать с использованием только 1 типа элемента (который будет описывать только маргины)).
Элементы выделенные розовым цветом внутри себя содержат другой блок, назовем его «пост».
Соответственно, рассмотрим из чего состоит блок пост:
Таким образом, блок «пост» состоит из 5 элементов.
На данном примере мы рассмотрели каким образом могут строиться блоки с применением методологии БЭМ (стоит сказать, что таким же образом может строиться любой уровень вложенности).
Теперь рассмотрим третью составляющую БЭМ — модификатор.
Модификатор может задавать как дополнительное поведение для блока\элемента, так и переопределять стандартное.
Самым ярким примером для понимания «модификатора» служит пример с кнопками.
Предположим, что в проекте используются кнопки типа:
Такая кнопка имеет некий padding слева и справа, шрифт и цвет background’а. Соответственно, мы представляем кнопку как .button и добавляем эти свойства ей.
Теперь, в проект на некоторых страницах необходимо добавить такие же кнопки, но, скажем, с background-color:red;
Решить данную задачу с использованием БЭМ можно очень просто:
Создать модификатор для блока: .button_red, для которой добавить заданное свойство.
Готово! В проекте появились красные кнопки с другим внешним видом. Причем нам не понадобилось создавать новые сущности.
Стиль описания БЭМ
БЭМ не декларирует «особого» стиля описания классов. Однако, де-факто используется следующий стиль:
1) Несколько слов в одном названии разделяются дефисом (например, блок main-page или my-super-main-list)
2) Элементы отделяются от блоков с использованием двух символов подчеркивания «__» (например, main-page__header или my-super-main-list__item)
3) Модификаторы отделяются одним символом подчеркивания «_» (например, main-page__header_strong или my-super-main-list_blue)
Префиксы
Иногда в проекте присутствуют префиксы. Они позволяют разработчиком точно определять какую логическую нагрузку несет тот или иной класс.
Например:
g- (global) префикс для глобальных классов. (Например, для задания невидимых элементов g-hidden.)
b- (block) префикс для выделения элементов, относящихся к структуре документа.
js- (JavaScript) префикс для выделения элементов для селекторов js.
Разметка страницы
Разметкой страницы занимается блок, который, к примеру, можно установить для body.
Соответственно элементы данного блока и описывают расположение остальных блоков на веб-странице.
Почему использование каскада — плохо?
У людей, впервые для себя открывших БЭМ может возникнуть вопрос — почему элементы описываются такими длинными цепочками? не проще ли:
.main-page
.main-page .header
и т.д.
Данный способ проще записывается, но, к сожалению, может нарушить независимость блоков.
Пример:
имеем следующую стуктуру:
.main-page .header .item .article .header .text .item ////
В этом случае .main-page .header будет применен не только к нужному .header, но и .item .article .header, что является ненужным. БЭМ предполагает уход от каскадных стилей (типа div .someClass li), которые:
1) повышают специфичность веб-страниц;
2) нарушают независимость блоков. (в отличие от БЭМ).
Именно поэтому, корректной структурой для БЭМ будет:
.main-page .main-page__header .main-page__item .article .article__header .article__text .main-page__item ////
Какие плюсы дает БЭМ?
0) Независимость блоков — за счет ухода от каскадности и отказа от описания в блоке «своего позиционирования»;
1) Повторяемость блоков — любой независимый блок можно повторять на любых страницах проекта. Возможно создать базу блоков, вследствие чего новые страницы будут создаваться подобно конструктору Лего;
2) Простота поддержки;
3) Структурированность кода.
Страница, сверстанная с использованием БЭМ может выглядеть больше, чем страницы, сверстанные без использования данной методологии, однако, представьте, если вы работаете с сайтом, где >20-30 уникальных страниц? В таком случае возможность повторного использования блоков и единая концепция позволяет:
1) намного быстрее принимать решения о модернизации страниц\блоков сайта;
2) уменьшает порог вступления в проект новых разработчиков.
Верстаем страницу
В качестве шаблона воспользуемся Corporate Blue.
Верстать будем главную страницу:
Изначально определим разметку страницы:
Здесь блок имеет 6 элементов.
По порядку:
1) Верхняя линия
2) Header
3) Меню
4) Слайдер
5) Main
6) Footer
Запишем эту структуру:
BEM-example
Зададим стили для блока и элементов:
.b-page < width:100%; margin:0; background-color:#f7f7f7; background:url("../img/bg.png"); font-family:Oswald,Tahoma; font-size:12px; >.b-page__head-line < background-color:#7e7e7e; height:5px; width:100%; >.b-page__line
Разберем header страницы
Он состоит из двух элементов:
Выделим картинку и зададим HTML-структуру:
HINT: второй div .b-search__input располагаем «впритык» к концу первого. Если так не сделать, то между двумя элементами будет пустое место. Его можно убрать либо изменив отступы, либо убрав в html коде пробелы.
.b-head < height:36px; >.b-head__logo
.b-head__search < float:right; >.b-search__input < display:inline-block; >.b-input < background-color: #f3f3f3; border:1px solid #e7e7e7; >.b-input_search < height:32px; padding:0 10px; width:135px; border-right:none; >.b-button
В данном блоке мы расположили элементы блока b-head слева и справа, а для input поиска задали стиль отображения inline-block.
Реакцию на работу кнопок\ссылок создавать не будем, так как цель статьи — показать на примере как можно верстать, используя БЭМ.
Переходим к меню
Заметим, что каждый элемент можно представить как самостоятельный блок (состоящий только из себя) и являющийся ссылкой. Заведем сущность ссылка (b-link) и определим для нее обычный стиль и шрифт для ссылки:
.b-link
Стили ссылок для меню определим в модификаторе .b-link_menu. Создадим HTML код:
HOME ABOUT US SERVICES PARTNERS CUSTOMERS PROJECTS CAREERS CONTACT
Активный пункт меню выделим как .b-link_menu_active.
Остается только добавить стили и меню готово:
.b-menu < margin:0; padding:0; list-style:none; width:100%; display:table; table-layout: fixed; >.b-link_menu < text-align:center; color:#bfbfbf; cursor:pointer; font-size:14px; height:38px; background-color:#f3f3f3; line-height:38px; border: 1px solid #e7e7e7; display:table-cell; text-decoration: none; >.b-link_menu_active
HINT: Если стоит задача сверстать набор элементов на полную ширину родителя, причем все элементы должны занимать одинаковое место, то решением можно считать:
1) Родителю задаем display: table; table-layout:fixed;
2) Дочерним элементам задаем display: table-cell;
Такое моделирование таблицы позволяет успешно решить поставленную задачу.
Переходим к слайдеру:
Здесь текст на слайдере необходимо вывести на картинке. Соответственно, для этого можно использовать картинку (на которой написать вручную текст), а можно воспользоваться особенностью position, либо z-index.
Второй способ гласит, что элементы с position: relative\ absolute отрисовываются отдельно от «остальных» элементов. Соответственно, эти элементы будут отображены «выше» остальных.
Третий способ: z-index задается для сестринских элементов. Представляет собой отображение по оси 0Z. Соответственно, тот элемент, у которого z-index выше, будет перекрывать собой те, у которого z-index ниже.
Зададим HTML структуру:
FUSCE VITAE NIBN QUIS DIAM FERMENTUMEtiam adipscing ultricies commodo.1LOREM IPSUM DOLOP2ULTRICIES PELLENTESQUE3ALIQUAM IPSUM4NULLAM SED MAURIS UT
Меню слайдера не использует так как предполагается, что при нажатии меняется текущее содержимое слайдера.
.b-slide__text < position:relative; top:40px; left:35px; z-index:10; >.b-slide__header < font-size:42px; color:#5a5a5a; >.b-slide__subtext < font-size:20px; color:#b0b0b0; >.b-slide__image < z-index: 0; margin-top:-62px; >.b-slider__list < height:50px; background-color:#f3f3f3; width:100%; margin-top:-2px; border-bottom: 1px solid #e7e7e7; >.b-slide-list < margin:0; padding:0; list-style:none; width:100%; display:table; table-layout:fixed; font-size:16px; color:#8f8f8f; >.b-slide-list__item < display:table-cell; >.b-slider-case-element__number < text-align:center; display:inline-block; width:22px; margin-left:12px; margin-top:12px; height:22px; background-color: #8f8f8f; color:#f3f3f3; >.b-slider-case-element < cursor:pointer; >.b-slider-case-element__number_active < background-color:#29c5e6; color:#fefefe; >.b-slider-case-element__text_active
Main-блок
Основной блок состоит из 6 элементов, каждый из которых содержит блок, наполненый:
а) картинками
б) текстом
в) ссылками
Блоки различаются между собой по ширине и высоте. Если высота будет вычислена при отрисовке страницы, то ширину мы можем задать разную с помощью модификаторов к элементам, в котором содержатся блоки. Выделим стандартный размер (первые два), малый (вторая линия) и длинный (соответственно третья линия):
.b-main__item < float:left; margin-left:10px; width:460px; >.b-main__item_small < width:300px >.b-main__item_long
Отдельно взятый блок будет выглядеть так:
.b-item-head__img < display:inline-block; background: url("../img/bg.png"); padding:2px; >.b-item-head__text < display:inline-block; >.b-item-head < line-height:16px; height:19px; background:url('../img/hbg.png') repeat; >.b-item-head__text < display:inline; font-size:16px; color:#8f8f8f; padding:3px; padding-right:10px; background: url("../img/bg.png"); >.b-company < width:100%; >.b-company__content < margin-top:15px; >.b-content__img < float:left; margin-right:20px; >.b-content__text < color:#8f8f8f; font-size:12px; >.b-link,.b-content__link
Аналогично отображаются другие блоки. (Меняем стили только внутренних элементов)
Переходим к footer
footer состоит из трех расположенных последовательно вложенных блоков:
1) Twitter
2) SiteMap
3) Social Networks
И четвертого, выровненного по правому краю — логотипу:
Разберем верстку каждого из этих блоков:
Состоит из времени, заголовка и текста.
TWITTER FEED 22 oct In ultricies pellentesque massa a porta. Aliquam ipsum enim, hendrerit ut porta nec, ullamcorper et nulla. In eget mi dui, sit amet scelerisque nunc.
В стилях задаем размер блока, цвета и прочие стили:
.b-twitter < width:300px; >.b-twitter__header,.b-sitemap__header,.b-networks__header < color:#fff; font-size:14px; width:100%; border-bottom:1px solid #878787; >.b-twitter__time < margin-top:5px; font-size:11px; color:#b4aeae; text-decoration:underline; >.b-twitter__text
Здесь же задали и стили заголовков для остальных блоков (т.к. они одинаковы)
Sitemap
Карта сайта состоит из блоков-ссылок, для которых с помощью модификаторов убираем нижнее подчеркивание и красим в белый цвет.
Эффект 2 колонок получаем за счет указания размера блока + одной группе ссылок задаем float:left, другой float: right:
SITEMAP Home About Services Partners Support Contact
.b-sitemap__links < float:left; >.b-link_white < color:#dbdbdb; >.b-link_undecorate < text-decoration:none; >.b-link_block < margin-top:8px; display:block; >.b-sitemap__links_right
Social
Здесь воспользуемся спрайтами. (Спрайт -картинка, которая содержит набор других картинок) Соответственно, с помощью Background-image + background-position для каждого элемента блока выбираем необходимые части картинки:
SOCIAL NETWORKS
.b-networks__icon < background:url("../img/social.png") 0 0; width:30px; height:30px; display: inline-block; margin-top:10px; margin-right:10px; >.b-networks__icon_small < width:16px; height:16px; margin-right:5px; >.b-networks__icon_twitter < background-position:0 0; >.b-networks__icon_facebook < background-position: -40px 0; >.b-networks__icon_google < background-position:-80px 0; >.b-networks__icon_vimeo < background-position:0 -38px; >.b-networks__icon_youtube < background-position: -23px -38px; >.b-networks__icon_flickr < background-position: -46px -38px; >.b-networks__icon_instagram < background-position: -69px -38px; >.b-networks__icon_rss
Малые иконки от «стандартных» отличаются только модификатором, задающим размер.
Footer logo
Для лого в футере странице воспользуемся уже готовым блоком b-logo. Вставим данный блок внутрь элемента, который будет отформатирован с помощью float:right;
Copyright 2012 Whitesquare. A pcklab creation
.b-footer__logo < float:right; margin-top:23px; margin-right:40px; >.b-footer-logo__img < float:right; >.b-footer-logo__copyright < clear:right; >.b-link_small < font-size:10px; >.b-footer-logo__copyright
Подведем итог
В данном обучающем посте мной была поставлена задача показать, что использование методологий (таких как БЭМ) позволяет упростить разработку веб-страниц за счет «единого словаря терминологий» и единой структуры страницы.
Также, данный пост является дополнением моего комментария (в начале поста) в котором я говорил о том, почему использовать стили на id — есть bad practices.
БЭМ — полный контроль над версткой
Впервые, узнав о БЭМ, показалось, что это только усложняет верстку, но углубившись в методологию и применив ее в проектах, приходит понимание, что БЭМ действительно то, чего не хватало для более комфортной верстки
Что такое БЭМ
Методологию БЭМ разработала компания Яндекс и кратко описывает ее так
БЭМ (Блок, Элемент, Модификатор) — компонентный подход к веб-разработке. В его основе лежит принцип разделения интерфейса на независимые блоки. Он позволяет легко и быстро разрабатывать интерфейсы любой сложности и повторно использовать существующий код, избегая «Copy-Paste».
Подробнее можно почитать на официальном сайте — https://ru.bem.info/
В статье постараюсь показать применение методологии БЭМ больше с практической стороны
В начале определимся, что из себя представляют Блок, Элемент и Модификатор. Все это — обычные классы для тегов, но написанные по определенным правилам
Рассмотрим простой макет
Посмотрите верстку на Codepen
Блок
Блок — основа верстки по БЭМ. Блок не зависит от других Блоков, может содержать Элементы и другие Блоки, и может быть использован неоднократно
Сначала посмотрим основы БЭМ на примере заголовка
На макете у секций Popular и Catalog есть соответствующие заголовки
Так как заголовок будет использоваться несколько раз, то сделаем его отдельным Блоком по БЭМ
Блоки в БЭМ должны быть независимыми от других Блоков, то есть для Блоков не задают свойств, которые как-либо могут повлиять на другие Блоки, например, внешние отступы margin
Описываем основные стили для Блока с учетом того, что внешние отступы margin не должны присутствовать в свойствах этого Блока
/* Блок title */ .title
Так как Блоки по БЭМ могут содержать в себе другие Блоки, создадим новый Блок для секции Popular — , и добавим в него уже созданный Блок
Popular
Теперь Блок
находится внутри Блока , но по прежнему остается независимым Блоком и не является Элементом для Блока
Чтобы Блок стал Элементом для Блока , необходимо Блоку добавить класс Элемента
Элемент
Класс Элемента состоит из названия Блока и названия Элемента через два нижних подчеркивания — __ , например, popular__title
Элементы могут содержать другие Элементы, но не должны быть частью другого Элемента
Элемент может быть только частью Блока
Неправильная структура
/* Неверно */ .
Правильная структура
/* Верно */ .
Добавляем Блоку класс Элемента — popular__title
Popular
Теперь Блок
является Элементом родительского Блока
Элемент по БЭМ обязательно должен использоваться только внутри родительского Блока, для которого он был создан, и не может использоваться вне его
Элементу мы уже можем добавлять внешние отступы margin , так как Элемент
вне родительского Блока использоваться не будет, и может взаимодействовать с другими Элементами
/* Блок title */ .title < font-size: 36px; font-weight: bold; text-align: center; >/* Элемент popular__title */ .popular__title
Теперь для Блока
будут также применены свойства Элемента
Аналогичным образом сделаем Блок
Элементом для Блока
Popular Catalog
Заголовку Catalog также добавим отступ снизу через класс Элемента catalog__title
/* Блок title */ .title < font-size: 36px; font-weight: bold; text-align: center; >/* Элемент popular__title */ .popular__title < margin-bottom: 30px; >/* Элемент catalog__title */ .catalog__title
Промежуточный итог для понимания:
Блок мы можем использовать неоднократно в любой секции, в любом месте верстки, так как он имеет только стили, отвечающие за отображение. У него нет внешних отступов margin , которые могли бы помешать использовать его в другом месте верстки. Блок имеет начальные стили и может использоваться без родительского Блока.
Но для того, чтобы Блок стал Элементом другого родительского Блока, мы добавляем класс Элемента состоящего из названия родительского Блока и названия Элемента через два нижних подчеркивания __ — popular__title . Теперь блок
становится Элементом родительского Блока и больше не может использоваться вне этого Блока, так как теперь имеет внешние отступы margin .
В то же время, мы можем добавлять дополнительные стили для Блока
через класс Элемента popular__title , если эти стили будут необходимы только в пределах родительского Блока
Если же дополнительные стили для Блока будут применяться неоднократно, и не только в определенном родительском Блоке, то для этого применяются дополнительные классы — Модификаторы
Модификатор
Название Модификатора записывается через одно нижнее подчеркивание после названия класса Блока или названия класса Элемента — _ , например title_large для Блока или popular__title_large для Элемента
На макете в секции Блок заголовка
имеет другой цвет и другой размер шрифта.
Так как эти стили мы планируем применять не только в этой секции, то сделаем отдельные классы Модификаторов, которые можно будет использовать неоднократно
Итак, есть Блок заголовка
со стилями по-умолчанию
/* Блок title */ .title
Мы хотим изменить цвет заголовка не везде сразу, а локально, к примеру, как на макете, заголовку Catalog секции
Добавляем Блоку
класс Модификатора — title_blue и получаем
Catalog
Добавляем стили для класса Модификатора
/* Модификатор title_blue для Блока title */ .title_blue
В итоге получаем заголовок со стилями по-умолчанию и стилями добавленными через класс Модификатора
Модификаторы можно совмещать, то есть использовать несколько разных Модификаторов одновременно для одного и того же Блока или Элемента
Добавим еще Модификатор title_large для заголовка, который будет добавлять стиль с другим размером шрифта
Catalog
/* Модификатор title_blue для Блока title */ .title_blue < color: #4a60d7; >/* Модификатор title_large для Блока title */ .title_large
Модификаторы можно создавать и для Элементов, синтаксически это будет выглядеть так — catalog__title_small , если мы точно уверены, что такой Модификатор будет использоваться только для Элемента
и только в родительском блоке
Важно понимать, что Модификаторы нельзя использоваться отдельно от Блока или Элемента, для которого создан этот Модификатор
Получаем следующий код
Popular Catalog
.title < font-size: 36px; font-weight: bold; text-align: center; >/* Модификатор title_blue для Блока title */ .title_blue < color: #4a60d7; >/* Модификатор title_large для Блока title */ .title_large < font-size: 48px; >/* Элемент popular__title */ .popular__title < margin-bottom: 30px; >/* Элемент catalog__title */ .catalog__title
Если в дальнейшем появятся другие секции, где необходим будет заголовок синего цвета, то добавим ему Модификатор title_blue или понадобится заголовок большего размера, добавим Модификатор title_large
About Subscribe
Практика
Посмотрим на макет еще раз, и выделим Блоки, которые используются неоднократно
- Блок с логотипом в и в
- Блок с телефоном в и в
- Блок заголовка в и в
- Блок карточки с изображением, названием, текстом и кнопкой
- Блок кнопки — ее выделим тоже в отдельный Блок, так как предполагаем ее использование вне карточек
Верстаем Header и Footer
Общие Блоки и используются и в и в
Так как Блоки и больше нигде использоваться не будут, то дополнительные стили для них в задаем через классы Элементов
Logo 8(800)000-00-00
/* Блок header */ .header < padding: 30px 0; background: #4a60d7; >/* Элемент header__flex */ .header__flex < display: flex; align-items: center; justify-content: space-between; >/* Блок logo */ .logo a < font-size: 36px; text-transform: uppercase; color: #fff; font-weight: 700; text-decoration: none; >/* Блок phone */ .phone < position: relative; >.phone:after < content: ''; width: 0%; height: 6px; background: #40ffdc; display: block; transition: all 0.6s ease; position: absolute; left: 0; right: 0; bottom: 0; border-radius: 3px; >.phone:hover:after < width: 100%; >.phone a < font-size: 24px; font-weight: bold; text-decoration: none; color: #fff; >/* Элемент footer__logo */ .footer__logo a < color: #4a60d7; >/* Элемент footer__phone */ .footer__phone a
То есть, создаем Блок один раз и используем его два раза, но так как у отличается фон, то при помощи класса Элемента footer__logo Блоку назначаем другой цвет
Тоже самое и с Блоком
Верстаем Popular и Catalog
Popular Popular One Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur, maiores. Buy Popular Two Consectetur adipisicing elit. Aspernatur, maiores. Buy Popular Three Consectetur adipisicing elit Buy Popular Four Aspernatur, maiores. Lorem ipsum dolor. Buy Catalog Catalog One Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur, maiores. Buy Catalog Two Consectetur adipisicing elit. Aspernatur, maiores. Buy Catalog Three Consectetur adipisicing elit Buy
Следите, чтобы в CSS-файле класс Модификатора, всегда был расположен ниже класса Блока и класса Элемента, чтобы у стилей Модификатора приоритет был выше
/* Блок popular */ .popular < padding: 60px 0; >/* Элемент popular__title */ .popular__title < margin-bottom: 30px; >/* Элемент popular__row */ .popular__row < display: flex; flex-wrap: wrap; margin: 0 -15px -30px; >/* Элемент popular__col */ .popular__col < width: 25%; padding: 0 15px 30px; >/* Блок title */ .title < font-size: 36px; font-weight: bold; text-align: center; >/* Модификатор title_blue для Блока title */ .title_blue < color: #4a60d7; >/* Модификатор title_large для Блока title */ .title_large < font-size: 48px; >/* Блок card */ .card < border-radius: 15px; overflow: hidden; height: 100%; display: flex; flex-direction: column; >/* Элемент card__image */ .card__image < height: 200px; overflow: hidden; width: 100%; >.card__image img < display: block; width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; >/* Модификатор card__image_large для Блока card__image */ .card__image_large < height: 250px; >/* Модификатор card__image_radius для Блока card__image */ .card__image_radius < border-radius: 15px; >/* Элемент card__body */ .card__body < padding: 15px; flex: 1; background: #f1f1f1; display: flex; flex-direction: column; >/* Модификатор card__body_white для Блока card__body */ .card__body_white < background: #fff; >/* Модификатор .card__body_shrink для Блока card__body */ .card__body_shrink < flex: 0; >/* Элемент card__title */ .card__title < font-size: 24px; text-align: center; margin-bottom: 15px; >/* Модификатор card__title_large для Элемента card__title */ .card__title_large < font-size: 30px; >/* Элемент card__title */ .card__text < text-align: center; font-size: 16px; flex: 1; margin-bottom: 15px; font-weight: 300; >/* Модификатор card__text_large для Элемента card__text */ .card__text_large < font-size: 18px; >/* Блок button */ .button < text-align: center; >.button a < display: inline-flex; padding: 0 15px; background: #4a60d7; align-items: center; justify-content: center; text-align: center; height: 40px; width: 100px; color: #fff; border-radius: 5px; text-decoration: none; transition: all 0.6s ease; >.button a:hover < background: #40ffdc; >/* Модификатор button_large для Блока button */ .button_large a < width: 150px; height: 50px; font-size: 22px; >/* Модификатор button_gray для Блока button */ .button_gray a < background: #333; >/* Блок catalog */ .catalog < background: #f1f1f1; padding: 60px 0; >/* Элемент catalog__title */ .catalog__title < margin-bottom: 60px; >/* Элемент catalog__row */ .catalog__row < display: flex; flex-wrap: wrap; margin: 0 -15px -30px; >/* Элемент catalog__col */ .catalog__col
Средняя карточка в секции Catalog значительно отличается от остальных карточек. Вместо того, чтобы придумывать новый класс этой карточке, копировать часть свойств и добавлять новые, просто добавим Модификаторы для отличающихся Элементов карточки, при этом основа карточки будет такой же, как и у остальных
Если в дальнейшем захотим создать карточку только с увеличенным изображением, аналогично добавим лишь один, уже существующий Модификатор, а остальные Элементы карточки оставим неизменными
Более сложная структура
В простых макетах обычно для именования Блока, Элемента или Модификатора хватает одного слова, но в более сложных макетах есть блоки со сложной вложенной структурой
Поэтому Блоки, Элементы и Модификаторы могут иметь более сложные названия — их можно записывать через дефис — —
Использовать такие более сложные названия нужно грамотно, чтобы не получалось такой структуры
Пример плохой структуры
Popular Gray
/* Блок popular-gray */ .popular-gray < padding: 60px 0; background: #efefef; >/* Элемент popular-gray__title-second */ .popular-gray__title-second < font-size: 24px; color: #333; text-align: center; margin-bottom: 30px; >/* Модификатор popular-gray__title-second_left-blue для Элемента popular-gray__title-second */ .popular-gray__title-second_left-blue
Такая структура не имеет смысла — слишком усложненная и не универсальная. Лучше внимательней продумать структуру такого Блока, упростить и вынести в отдельные Блоки, Элементы, Модификаторы
Пример хорошей структуры
Popular Gray
/* Блок popular */ .popular < padding: 60px 0; >/* Модификатор popular_gray для Блока popular */ .popular_gray < background: #efefef; >/* Элемент popular__title */ .popular__title < margin-bottom: 30px; >/* Блок title */ .title < font-size: 36px; color: #333; text-align: center; >/* Модификатор title_second для Блока title */ .title_second < font-size: 24px; >/* Модификатор title_left для Блока title */ .title_left < text-align: left; >/* Модификатор title_blue для Блока title */ .title_blue
Такая структура намного более читаема, гибка, и логична.
Рассмотрим пример с более сложной структурой
Ниже макет с корзиной товаров
Сложность данного макета в том, что здесь есть блоки с аналогичным содержимым, например, есть количество товаров в карточке и общее количество товаров ниже. Есть стоимость товара в карточке и есть общая стоимость всех товаров, а также в карточке есть цена со скидкой и цена без скидки
Как и ранее, просто необходимо выделить отдельные блоки
- Есть основной Блок cart , значит все его дочерние Элементы будут именоваться через два нижних подчеркивания — cart__title , cart__cards , cart__card , cart__cost и так далее
- Замечаем, что Элемент cart__card имеет свою структуру и так же, как и Блок cart имеет Элемент цены. Второй Элемент cart__cost внутри карточки мы не можем создать, так как он уже используется для Элемента общей цены, поэтому карточку корзины товаров выделяем в отдельный Блок cart-card , который будет использоваться только в секции корзины товаров и его Элемент цены будет называться cart-card__cost . Заметьте, что карточку корзины мы не можем выделить в Блок card , так как card у нас уже используется в БлокахPopular и Catalog
- Также видим, что в карточке есть два вида цены — без скидки и со скидкой. В данном случае есть два варианта: либо выделить Элемент в отдельный Блок cart-card-cost и сделать Элементы cart-card-cost__old и cart-card-cost__current , но его структура не настолько сложна, чтобы выделять цену карточки в отдельный Блок. Поэтому сделаем их ЭлементамиБлока cart-card , и назовем их cart-card__cost-old и cart-card__cost-current , таким образом они будут элементами карточки, но мы также понимаем, что эти классы относятся именно к цене
Cart Lorem ipsum dolor. 699 $ 499 $ 2 Delete Ipsum dolor sit amet. 799 $ 599 $ 3 Delete Assumenda, nemo. 499 $ 399 $ 8 Delete Quantity: 13 Cost: 1497 $ Submit
/* Блок card */ .cart < padding: 60px 0; >/* Элемент cart__title */ .cart__title < margin-bottom: 60px; >/* Элемент cart__cards */ .cart__cards < margin-bottom: 60px; >.cart__card:not(:last-child) < margin-bottom: 30px; >/* Элемент cart__body */ .cart__body < text-align: center; >/* Элемент cart__body */ .cart__quantity < margin-bottom: 30px; font-size: 18px; font-weight: 300; >.cart__quantity span < font-size: 24px; font-weight: 700; >/* Элемент cart__cost */ .cart__cost < margin-bottom: 30px; font-size: 18px; font-weight: 300; >.cart__cost span < font-size: 24px; font-weight: 700; >/* Элемент cart__button */ .cart__button a < font-size: 24px; width: auto; padding: 0 60px; height: 60px; >/* Блок cart-card */ .cart-card < display: flex; border-radius: 10px; overflow: hidden; background: #f5f5f5; >/* Элемент cart-card__image */ .cart-card__image < width: 200px; flex-shrink: 0; border-radius: 10px; overflow: hidden; margin-right: 30px; >.cart-card__image img < -o-object-fit: cover; object-fit: cover; width: 100%; height: 100%; display: block; >/* Элемент cart-card__body */ .cart-card__body < flex: 1; display: flex; align-items: center; padding: 30px 0; >/* Элемент cart-card__title */ .cart-card__title < flex: 1; >.cart-card__title a < font-size: 18px; font-weight: 300; text-decoration: none; color: #000; transition: all 0.6s ease; >.cart-card__title a:hover < color: #4a60d7; >/* Элемент cart-card__cost */ .cart-card__cost < width: 100px; flex-shrink: 0; text-align: center; >/* Элемент cart-card__cost-old */ .cart-card__cost-old < text-decoration: line-through; font-size: 14px; >/* Элемент cart-card__cost-current */ .cart-card__cost-current < font-size: 18px; color: #4a60d7; >/* Элемент cart-card__quantity */ .cart-card__quantity < width: 100px; flex-shrink: 0; text-align: center; >/* Элемент cart-card__delete */ .cart-card__delete < text-align: center; width: 100px; flex-shrink: 0; >.cart-card__delete a
Послесловие
Когда я знакомился с БЭМ по документации, статьям, даже по видео на Youtube, общей картины у меня не складывалось за недостатком практики, и я не применял БЭМ, так как до конца не понимал всей идеи.
Первый проект, который я попытался сверстать по БЭМ, занял у меня на порядок больше времени, и получился не совсем корректен, но в голове все потихоньку раскладывалось по полочкам, и сейчас все проекты верстаю только по БЭМ
Верстка получается структурированой, гибкой, понятной, в нее легко вносить изменения, проще делать адаптивность, проще давать названия классам и чувствуешь, что получаешь больше контроля над версткой
В этой статье я постарался показать, как ко мне приходило понимание БЭМ, может кому-то такой подход окажется понятнее, чем другие
Рекомендую в свободное время сверстать любой макет по БЭМ, на практике становится все намного понятней
Все частные случаи применения БЭМ невозможно охватить одной статьей, но я постарался уместить максимум базовой информации в этих примерах
Очень надеюсь, что статья будет полезна, и вы также осознаете все преимущества верстки по методологии БЭМ
Полезные ссылки
Быстрый старт
БЭМ (Блок, Элемент, Модификатор) — компонентный подход к веб-разработке. В его основе лежит принцип разделения интерфейса на независимые блоки. Он позволяет легко и быстро разрабатывать интерфейсы любой сложности и повторно использовать существующий код, избегая «Copy-Paste».
Содержание
- Блок
- Элемент
- Когда создавать блок, когда — элемент?
- Модификатор
- Микс
- Файловая структура
Блок
Функционально независимый компонент страницы, который может быть повторно использован. В HTML блоки представлены атрибутом class .
- Название блока характеризует смысл («что это?» — «меню»: menu , «кнопка»: button ), а не состояние («какой, как выглядит?» — «красный»: red , «большой»: big ).
Пример
div class="error"> div> div class="red-text"> div>
- Блок не должен влиять на свое окружение, т. е. блоку не следует задавать внешнюю геометрию (в виде отступов, границ, влияющих на размеры) и позиционирование.
- В CSS по БЭМ также не рекомендуется использовать селекторы по тегам или id .
Таким образом обеспечивается независимость, при которой возможно повторное использование или перенос блоков с места на место.
Принцип работы с блоками
Вложенность
- Блоки можно вкладывать друг в друга.
- Допустима любая вложенность блоков.
Пример
header class="header"> div class="logo"> div> form class="search-form"> form> header>
Элемент
Составная часть блока, которая не может использоваться в отрыве от него.
- Название элемента характеризует смысл («что это?» — «пункт»: item , «текст»: text ), а не состояние («какой, как выглядит?» — «красный»: red , «большой»: big ).
- Структура полного имени элемента соответствует схеме: имя-блока__имя-элемента . Имя элемента отделяется от имени блока двумя подчеркиваниями ( __ ).
Пример
form class="search-form"> input class="search-form__input"> button class="search-form__button">Найти button> form>
Принципы работы с элементами
- Вложенность
- Принадлежность
- Необязательность
Вложенность
- Элементы можно вкладывать друг в друга.
- Допустима любая вложенность элементов.
- Элемент — всегда часть блока, а не другого элемента. Это означает, что в названии элементов нельзя прописывать иерархию вида block__elem1__elem2 .
Пример
form class="search-form"> div class="search-form__content"> input class="search-form__input"> button class="search-form__button">Найти button> div> form> form class="search-form"> div class="search-form__content"> input class="search-form__content__input"> button class="search-form__content__button">Найти button> div> form>
Имя блока задает пространство имен, которое гарантирует зависимость элементов от блока ( block__elem ).
Блок может иметь вложенную структуру элементов в DOM-дереве:
Пример
div class="block"> div class="block__elem1"> div class="block__elem2"> div class="block__elem3"> div> div> div> div>
Однако эта же структура блока в методологии БЭМ всегда будет представлена плоским списком элементов:
Пример
.block <> .block__elem1 <> .block__elem2 <> .block__elem3 <>
Это позволяет изменять DOM-структуру блока без внесения правок в коде каждого отдельного элемента:
Пример
div class="block"> div class="block__elem1"> div class="block__elem2"> div> div> div class="block__elem3"> div> div>
Структура блока меняется, а правила для элементов и их названия остаются прежними.
Принадлежность
Элемент — всегда часть блока и не должен использоваться отдельно от него.
Пример
form class="search-form"> input class="search-form__input"> button class="search-form__button">Найти button> form> form class="search-form"> form> input class="search-form__input"> button class="search-form__button">Найти button>
Необязательность
Элемент — необязательный компонент блока. Не у всех блоков должны быть элементы.
Пример
div class="search-form"> input class="input"> button class="button">Найти button> div>
Когда создавать блок, когда — элемент?
Создавайте блок
Если фрагмент кода может использоваться повторно и не зависит от реализации других компонентов страницы.
Создавайте элемент
Если фрагмент кода не может использоваться самостоятельно, без родительской сущности (блока).
Исключение составляют элементы, реализация которых для упрощения разработки требует разделения на более мелкие части — подэлементы. В БЭМ-методологии нельзя создавать элементы элементов. В подобном случае вместо элемента необходимо создавать служебный блок.
Модификатор
Cущность, определяющая внешний вид, состояние или поведение блока либо элемента.
- Название модификатора характеризует внешний вид («какой размер?», «какая тема?» и т. п. — «размер»: size_s , «тема»: theme_islands ), состояние («чем отличается от прочих?» — «отключен»: disabled , «фокусированный»: focused ) и поведение («как ведет себя?», «как взаимодействует с пользователем?» — «направление»: directions_left-top ).
- Имя модификатора отделяется от имени блока или элемента одним подчеркиванием ( _ ).
Типы модификаторов
Булевый
- Используют, когда важно только наличие или отсутствие модификатора, а его значение несущественно. Например, «отключен»: disabled . Считается, что при наличии булевого модификатора у сущности его значение равно true .
- Структура полного имени модификатора соответствует схеме:
- имя-блока_имя-модификатора ;
- имя-блока__имя-элемента_имя-модификатора .
Пример
form class="search-form search-form_focused"> input class="search-form__input"> button class="search-form__button search-form__button_disabled">Найти button> form>
Ключ-значение
- Используют, когда важно значение модификатора. Например, «меню с темой оформления islands »: menu_theme_islands .
- Структура полного имени модификатора соответствует схеме:
- имя-блока_имя-модификатора_значение-модификатора ;
- имя-блока__имя-элемента_имя-модификатора_значение-модификатора .
Пример
form class="search-form search-form_theme_islands"> input class="search-form__input"> button class="search-form__button search-form__button_size_m">Найти button> form> form class="search-form search-form_theme_islands search-form_theme_lite"> input class="search-form__input"> button class="search-form__button search-form__button_size_s search-form__button_size_m"> Найти button> form>
Принципы работы с модификаторами
Модификатор нельзя использовать самостоятельно
С точки зрения БЭМ-методологии модификатор не может использоваться в отрыве от модифицируемого блока или элемента. Модификатор должен изменять вид, поведение или состояние сущности, а не заменять ее.
Пример
form class="search-form search-form_theme_islands"> input class="search-form__input"> button class="search-form__button">Найти button> form> form class="search-form_theme_islands"> input class="search-form__input"> button class="search-form__button">Найти button> form>
Микс
Прием, позволяющий использовать разные БЭМ-сущности на одном DOM-узле.
- совмещать поведение и стили нескольких сущностей без дублирования кода;
- создавать семантически новые компоненты интерфейса на основе имеющихся.
Пример
div class="header"> div class="search-form header__search-form"> div> div>
В данном примере мы совместили поведение и стили блока search-form и элемента search-form блока header . Такой подход позволяет нам задать внешнюю геометрию и позиционирование в элементе header__search-form , а сам блок search-form оставить универсальным. Таким образом, блок можно использовать в любом другом окружении, потому что он не специфицирует никакие отступы. Это позволяет нам говорить о его независимости.
Файловая структура
Принятый в методологии БЭМ компонентный подход применяется и к организации проектов в файловой структуре. Реализации блоков, элементов и модификаторов делятся на независимые файлы-технологии, что позволяет нам подключать их опционально.
- Один блок — одна директория.
- Имена блока и его директории совпадают. Например, блок header — директория header/ , блок menu — директория menu/ .
- Реализация блока разделяется на отдельные файлы-технологии. Например, header.css , header.js .
- Директория блока является корневой для поддиректорий соответствующих ему элементов и модификаторов.
- Имена директорий элементов начинаются с двойного подчеркивания ( __ ). Например, header/__logo/ , menu/__item/ .
- Имена директорий модификаторов начинаются с одинарного подчеркивания ( _ ). Например, header/_fixed/ , menu/_theme_islands/ .
- Реализации элементов и модификаторов разделяются на отдельные файлы-технологии. Например, header__input.js , header_theme_islands.css .
Пример
search-form/ # Директория блока search-form __input/ # Поддиректория элемента search-form__input search-form__input.css # Реализация элемента search-form__input # в технологии CSS search-form__input.js # Реализация элемента search-form__input # в технологии JavaScript __button/ # Поддиректория элемента search-form__button search-form__button.css search-form__button.js _theme/ # Поддиректория модификатора # search-form_theme search-form_theme_islands.css # Реализация блока search-form, имеющего # модификатор theme со значением islands # в технологии CSS search-form_theme_lite.css # Реализация блока search-form, имеющего # модификатор theme со значением lite # в технологии CSS search-form.css # Реализация блока search-form # в технологии CSS search-form.js # Реализация блока search-form # в технологии JavaScript
Такая файловая структура позволяет легко поддерживать и повторно использовать код.
Разветвленная файловая структура предполагает, что в production код будет собираться в общие файлы проекта.
Придерживаться рекомендуемой файловой структуры не обязательно. Вы можете использовать любую альтернативную структуру проекта, соответствующую принципам организации файловой структуры БЭМ, например:
История создания БЭМ
История БЭМ началась в 2005 году. Тогда, с точки зрения интерфейса, обычный проект Яндекса был набором статических HTML-страниц, которые использовались как основа для создания шаблонов на XSL.
HTML-страницы хранились в отдельной директории, которая имела подобную структуру:
about.html index.html … project.css project.js i/ yandex.png
- Для каждой страницы создавался отдельный HTML-файл. В верстке использовались id и классы .
- Скрипты хранились в одном файле для всего проекта — project.js . JavaScript использовался как вспомогательный инструмент для оживления страницы, поэтому project.js был небольшим.
- Картинки складывались в отдельную директорию. Необходимость поддержки IE 5 и отсутствие CSS3 в браузерах, вынуждало использовать картинки для реализации любого оформления, даже для закругленных уголков.
- Стили и скрипты писались в файлах: project.css , project.js . Для отделения стилей разных частей страницы использовались комментарии с указанием начала и конца:
/* Content container (begin) */ #body < font: 0.8em Arial, sans-serif; margin: 0.5em 1.95% 0.5em 2%; > /* Content container (end) */ /* Graphical banner (begin) */ .banner < text-align: center; > /* Graphical banner (end) */
Сверстанные статические HTML-страницы нарезались в XSL-шаблоны. Если HTML изменялся, все правки было необходимо переносить вручную в XSL. И наоборот, изменения в шаблонах требовали правок в HTML (для поддержания статического HTML в актуальном состоянии).
Зарождение основ методологии
В 2006 году началась работа над первыми большими проектами — Яндекс.Музыка и Я.ру. Эти проекты с десятками страниц выявили основные недостатки текущего подхода к разработке:
- невозможно внести изменения в код одной страницы, не затрагивая код другой;
- сложно подбирать названия классам.
Типичный CSS того времени содержал длинный каскад:
/* Albums (begin) */ .result .albums .info < padding-right: 8.5em; > .result .albums .title < float: left; padding-bottom: 0.3em; > .result .albums .album .listen < float: left; padding: 0.3em 1em 0 1em; > /* Albums (end) */
Совместно использовались селекторы по тегам и идентификаторам:
/* Картинки на фоне (begin) */ #foot div < height: 71px; background: transparent url(../i/foot-1.png) 4% 50% no-repeat; > #foot div div < background-position: 21%; background-image: url(../i/foot-2.png); > #foot div div div < background-position: 38%; background-image: url(../i/foot-3.png); > /* Картинки на фоне (end) */
Верстка большого проекта была неуправляемой. Чтобы избежать этого, нужно было определить правила работы с понятиями класса, тега, визуального компонента и др.
Появление блоков
Основное время разработчиков тратилось на создание HTML-структуры страницы и написание CSS-стилей для нее. JavaScript воспринимался лишь как сопутствующая технология.
Чтобы ускорить разработку, требовалось облегчить поддержку HTML и CSS отдельных компонентов страницы. Для этого мы ввели понятие блока.
Блоком называлась часть дизайна страницы или раскладки со своим специфическим и уникальным значением, определенным семантически или визуально.
В большинстве случаев любой компонент на странице (сложный или простой) рассматривался как блок. HTML-контейнер каждого блока получал уникальный CSS-класс с тем же именем, что и у блока.
Классам блоков мы добавили префиксы ( b- , c- , g- ), чтобы отличать их от внутренних классов:
- b- — block. Независимый блок, может использоваться в любом месте страницы.
- с- — control. Контрол (независимый блок), с которым ассоциирован JavaScript-объект, обеспечивающий его функциональность. Может использоваться в любом месте страницы.
- g- — global. Глобальное определение, используется по необходимости. Количество сведено к минимуму.
Кроме префиксов использовались постфиксы, например:
Стиль применяется в отсутствие JavaScript. Если JavaScript включен, то при загрузке страницы вызывается метод init() в onload, и постфикс удаляется из всех классов. Таким образом «включался» JavaScript для блоков.
Появление элементов
В HTML-контейнере, формирующем блок, некоторые узлы получали четкий CSS-класс. Это не только облегчило создание стилистических правил, независящих от имени тега, но и позволяло присваивать семантически значимую роль каждому узлу. Такие внутренние узлы мы назвали элементами блока, или просто элементами.
Ключевое различие между блоком и элементом в тот момент:
- элемент не может существовать вне контекста родительского блока;
- из блока нельзя извлечь ни один элемент.
Если элемент способен существовать вне блока, он становится блоком.
Позже стало возможным вынимать некоторые элементы из блока, сохраняя при этом рабочее состояние самого блока.
Элементы с большим количеством кода выделялись комментариями.
/* Head (begin) */ .b-head < … >/* Logo (begin) */ .b-head .logo < … >.b-head .logo a < … >/* Logo (end) */ /* Right side (begin) */ .b-head .right < … >/* Info (begin) */ .b-head .info < … >.b-head .info .exit a < … >/* Info (end) */ /* Search (begin) */ .b-head .search < … >.b-head .search div div, .b-head .search div div i < … >/* Search (end) */ /* Right side (end) */ /* Head (end) */
Унификация файловой структуры проекта
Разработчики интерфейсов обычно поддерживают несколько проектов одновременно. Работать с разными проектами легче, если все они имеют одинаковую (или очень похожую) файловую структуру. Поэтому мы унифицировали структуры репозиториев разных проектов.
Начали с того, что CSS, JavaScript и картинки стали складывать в отдельные директории.
JavaScript применялся все чаще, в проект подключались дополнительные компоненты и библиотеки.
Типичная структура верстки проекта 2006 года:
index.html css/ yaru.css yaru-ie.css js/ yaru.js i/ yandex.png
Основной код для IE мы писали в общем CSS-файле, например, yaru.css .
/* Common definitions (begin) */ body < font: 0.8em Arial, sans-serif; padding: 0 0 2em 0; background: #fff; > * html body < font-size: 80%; >
Специфичные правила (временные решения), работающие только в IE, создавались в отдельном файле. В имя файла добавлялся специальный указатель ie — yaru-ie.css .
/* Common blocks (begin) */ /* Artist (begin) */ .b-artist .i i < top: expression(7 + (90 - this.parentNode.getElementsByTagName('img')[0].height)/2); filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../i/sticker-lt.png', sizingMethod='crop'); >
Зачатки общепортального фреймворка
При верстке нескольких проектов с похожим дизайном появлялись общие блоки.
Портал Яндекса в то время содержал больше 100 разных сервисов, выполненных в одном стиле. Для такого объема данных «Copy-Paste» из проекта в проект уже не подходил.
Появилось общее хранилище повторно используемых компонентов, которое называлось общая библиотека блоков или просто — Common .
Первые блоки, которые вошли в Common : шапка, подвал и стили для статического текста.
Файлы блоков хранились на выделенном внутреннем сервере разработчиков (common.cloudkill.yandex.ru в примере ниже).
Это было началом работы нашего общепортального фреймворка. Cтили из него подключались в основной проектный файл при помощи импортов непосредственно с сервера:
@import url(http://common.cloudkill.yandex.ru/css/global.css); @import url(http://common.cloudkill.yandex.ru/css/head/common.css); @import url(http://common.cloudkill.yandex.ru/css/static-text.css); @import url(http://common.cloudkill.yandex.ru/css/list/hlist.css); @import url(http://common.cloudkill.yandex.ru/css/list/hlist-middot.css); @import url(slider.css); /* Header (begin) */ /* Service (begin) */ .b-head .service h1 < … >.b-head .service h1, .b-head .service h1 a, .b-head .service h1 b
Возникла проблема: большое количество импортов замедляло загрузку страницы. Было принято решение прекомпилировать стили (и позже JavaScript-файлы) перед выкладкой.
Компиляция заменяет @import на содержимое внешних файлов (это называется inlining ) и оптимизирует код. Например, убирает ненужные браузеру пробелы и комментарии.
Наш внутренний инструмент для оптимизации вырос из простого Perl-скрипта в отдельный open-source-проект borschik.
Верстка независимыми блоками
К осени 2007 года правила верстки устоялись. Мы увидели практическую пользу от нового подхода, поэтому было решено рассказать об этом вне Яндекса.
На ClientSide’07 был сделан доклад про верстку независимыми блоками, которая на тот момент составляла основу наших HTML-страниц.
В докладе официально вводилось понятие блока:
Блоком будем называть фрагмент страницы, который описывается своей разметкой и стилями.
Более позднее описание.
Блоки делились на простые и составные.
В простые блоки нельзя вкладывать другие блоки, в составные — можно.
Это было неверно: мы неоднократно сталкивались с тем, что даже в самые простые блоки вкладывались другие, и приходилось переделывать верстку. В итоге мы пришли к противоположному принципу:
Любой блок должен позволять вкладывать в него другой блок, когда это возможно.
Правила независимости блоков
Сформировались первые правила независимости блока:
- Для описания элемента используется class , но не id .
- Каждый блок имеет префикс.
- В таблице стилей нет классов вне блоков.
Важным решением был отказ от id .
Теперь мы могли:
- отображать на странице один и тот же блок несколько раз;
- использовать на одном DOM-узле несколько классов (что нам пригодилось в дальнейшем).
Правила полной независимости блоков
С текущей схемой оставался ряд проблем с CSS:
- CSS мог работать неправильно, если в страницу включался код, написанный сторонними разработчиками (например, библиотеки).
- Блоки могли неправильно отображаться из-за конфликта имен элементов.
- Селекторы по тегам могли охватывать больше HTML-элементов, чем было задумано.
Поэтому мы сформулировали правила более строгой независимости блоков под названием абсолютно независимые блоки (АНБ):
- Не писать селекторы по тегам. Стили блоков и элементов описывать через селекторы классов. Пример
.b-user b -> .b-user .first-letter
- Всем классам внутри блока давать имена, начинающиеся с имени этого блока. Пример
.b-user .first-letter -> .b-user-first_letter
Мы понимали, что наличие класса у каждого DOM-узла существенно увеличивает объем HTML-кода. На тот момент мы считали, что это дорого, и применяли такой подход в исключительных случаях.
Первые правила именования — префиксы
Так как распространенной проблемой в программировании является подбор имен, мы решили задавать имена блоков с помощью разных префиксов с разной семантикой:
Появление модификации блоков
Работая с блоками, мы поняли, что они могут иметь разные состояния.
Например, «Кнопка» (блок button ) может быть:
- маленькой;
- нормальной;
- большой.
Вместо того, чтобы создавать три разных блока, мы начали делать модификации.
Модификацию мы определили как особое состояние блока или как метку, несущую определенное свойство блоку.
Модификация определялась именем (например, size ) и значением (например, small , normal или big ).
Возможные варианты модификации:
- Блок может изменить свой внешний вид в зависимости от того, где он находится. Это модификация от контекста.
- Можно добавить блоку второй класс. Это модификация постфиксом, она не зависит от контекста. Пример
Общепортальный фреймворк — Лего
Весной 2008 года была поставлена задача создать брендбук, описывающий наш портальный стиль. Решили начать работу с написания HTML/CSS кода.
Проект получил название Лего .
Структура репозитория
На верхнем уровне репозиторий разделен по технологиям:
css/ html/ js/ xml/ xsl/
Директория каждой технологии имеет свою структуру.
CSS распределяется на следующие директории:
- block — общепортальные блоки;
- util — блоки, которые имеют смысл вне Яндекса, их можно выложить в open source;
- service — стили для конкретных сервисов Яндекса. Подключив их к сервису, можно отобразить шапку или подвал.
Пример
css/ block/ b-dropdown/ b-dropdown.css service/ auto/ block/ b-head-logo-auto.css head.css util/ b-hmenu/ b-hmenu.css
Структура директории HTML аналогична CSS:
html/ block/ b-dropdown.html service/ auto/ l-head.html util/ b-hmenu.html
JS находится в зачаточном состоянии и складывается в одну директорию:
js/ check-is-frame.js check-session.js clean-on-focus.js dropdown.js event.add.js event.del.js
У каждого сервиса есть XML-файл, использующийся для построения шапки:
xml/ block/ b-head-tabs-communication.xml common-services.ru.xml head-messages.ru.xml service/ auto/ head.xml
XSL блоков находится в одной директории. Каждому блоку соответствует один файл:
xsl/ block/ b-dropdown.xsl b-head-line.xsl i-common.xsl i-locale.xsl l-foot.xsl l-head.xsl
Лего подключается в проекты с помощью svn:externals.
При финальной сборке проекта код библиотеки полностью включается в проект, что можно сравнить со статической линковкой.
Такой подход позволяет выпускать версии сервисов с разными версиями Лего и переходить на новую версию тогда, когда это удобно команде проекта.
CSS-файлы
CSS-файлы, подключавшиеся на страницах, состояли из @import ‘ов реализации блоков.
@import url(../../block/l-head/l-head.css); @import url(../../block/b-head-logo/b-head-logo.css); @import url(../../block/b-head-logo/b-head-logo_name.css); @import url(block/b-head-logo-auto.css);
Эти @import ‘ы писались вручную.
Правила именования
Именование файлов еще не устоялось — мы пробуем разные варианты.
Общепортальный фреймворк — Лего 1.2 (2008)
Структура репозитория
В рамках версии Лего 1.2, был произведен рефакторинг, и структура репозитория проекта изменилась.
common/ css/ js/ xml/ xsl/ example/ html/ service/ auto/ css/ xml/
Убрано разделение на util и block , общий CSS находится в common/css .
От идеи выноса кода в open source на тот момент отказались и вернулись к ней только через два года.
common/ css/ b-dropdown/ arr/ b-dropdown.arr.css b-dropdown.arr.ie.css b-dropdown.css b-dropdown.ie.css
Всё, что находилось в опциональном CSS (файлах b-dropdown_arr.css , b-dropdown_arr.ie.css ), вынесено в директории ( arr/ ). В основном файле блока стало меньше кода.
Правила именования
Файлы для IE переименованы: указатель специфичности файла для IE был частью имени файла, а стал суффиксом. Было -ie.css , — стало .ie.css . Расширения файлов теперь могут состоять из нескольких слов.
Для модификации постфиксом вместо дефиса начали использовать подчеркивание. Это позволило визуально отделить имя блока от имени модификатора, что позже пригодилось при реализации инструментов, упрощающих работу с кодом.
Лего 2.0. Появление БЭМ
В марте 2009 года вышла версия Лего 2.0.
Этим событием оканчивается верстка независимыми блоками и начинается БЭМ .
БЭМ — аббревиатура от Блок, Элемент, Модификатор. Это три ключевые сущности, которые мы используем при разработке компонентов интерфейса.
Что же принципиально изменилось с выходом версии 2.0?
Основное изменение — мы вывели вперед блоки, а не технологии. Отныне блоки первичны, а технологии их реализации — вторичны.
Реализацию каждого блока разместили в отдельной директории, технологии — это файлы внутри нее. Также появилась документация к блоку — файл .wiki внутри блока.
Независимый блок
Может быть использован в любом месте страницы.
В XML блок представлен тегом в пространстве имен lego :
lego:l-head> lego:b-head-logo>
HTML-класс блока соответствует имени этого тега:
table class="l-head"> div class="b-head-logo">
CSS-правила пишутся на класс:
.l-head .b-head-logo
Все файлы ( css , js , html , xsl ), относящиеся к блоку, хранятся в его директории:
common/ block/ b-head-logo/ b-head-logo.css b-head-logo.xsl b-head-logo.js b-head-logo.wiki
Элемент
Составная часть блока, которая не может использоваться в отрыве от него.
В XML элемент представлен в пространстве имен lego без префикса:
lego:b-head-logo> lego:name/> lego:b-head-logo>
Класс в HTML соответствует имени этого элемента без префикса.
div class="b-head-logo"> span class="name">Авто span> div>
CSS-правила пишутся на класс:
.b-head-logo .name
Файлы элемента хранятся в отдельной директории.
common/ block/ b-head-logo/ name/ b-head-logo.name.css b-head-logo.name.png b-head-logo.name.wiki
Имена файлов элементов пишутся через точку: b-head-logo.name.css
Модификатор
Определяет внешний вид, состояние и реже поведение блока.
В XML модификатор представлен атрибутом в пространстве имен lego :
lego:b-head-tabs lego:theme="grey">
В HTML используется дополнительный класс:
div class="b-head-tabs b-head-tabs_grey">. div>
CSS-правила пишутся на класс:
.b-head-tabs_grey
Файлы для модификатора находятся в отдельной директории. Имя директории модификатора начинается с подчеркивания:
common/ block/ b-head-logo/ _theme/ b-head-logo_gray.css b-head-logo_gray.png b-head-logo_gray.wiki
Декларация используемых блоков
Все компоненты Лего описываются в XML-файле.
lego:page> lego:l-head> lego:b-head-logo> lego:name/> lego:b-head-logo> lego:b-head-tabs type="search-and-content"/>
Из него генерируются CSS-файлы.
@import url(../../common/block/global/_type/global_reset.css); @import url(../../common/block/l-head/l-head.css); @import url(../../common/block/b-head-logo/b-head-logo.css); @import url(../../common/block/b-head-logo/name/b-head-logo.name.css); @import url(../../common/block/b-head-tabs/b-head-tabs.css); @import url(../../common/block/b-dropdown/b-dropdown.css); @import url(../../common/block/b-dropdown/text/b-dropdown.text.css); @import url(../../common/block/b-pseudo-link/b-pseudo-link.css); @import url(../../common/block/b-dropdown/arrow/b-dropdown.arrow.css); @import url(../../common/block/b-head-search/b-head-search.css); @import url(../../common/block/b-search/b-search.css); @import url(../../common/block/b-search/input/b-search.input.css); @import url(../../common/block/b-search/sample/b-search.sample.css); @import url(../../common/block/b-search/precise/b-search.precise.css); @import url(../../common/block/b-search/button/b-search.button.css); @import url(../../common/block/b-head-userinfo/b-head-userinfo.css); @import url(../../common/block/b-user/b-user.css); @import url(block/b-head-logo/b-head-logo.css); @import url(block/b-head-search/b-head-search.css);
На примере этого файла видно, что сначала указывается общий код, а потом добавляются стили, чтобы привести Лего-блоки к дизайну проекта.
Из XML-декларации генерируются и JS-файлы.
include("../../common/block/i-locale/i-locale.js"); include("../../common/block/b-dropdown/b-dropdown.js"); include("../../common/block/b-search/sample/b-search.sample.js"); include("../../common/block/b-head-userinfo/user/b-head-userinfo.user.js");
А также XSL-файлы.
xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> xsl:import href="../../common/block/i-common/i-common.xsl"/> xsl:import href="../../common/block/i-items/i-items.xsl"/> xsl:import href="../../common/block/l-head/l-head.xsl"/> xsl:import href="../../common/block/b-head-logo/b-head-logo.xsl"/> xsl:import href="../../common/block/b-head-logo/name/b-head-logo.name.xsl"/> xsl:import href="../../common/block/b-head-tabs/b-head-tabs.xsl"/> xsl:import href="../../common/block/b-dropdown/b-dropdown.xsl"/> xsl:import href="../../common/block/b-pseudo-link/b-pseudo-link.xsl"/> xsl:import href="../../common/block/b-head-search/b-head-search.xsl"/> xsl:import href="../../common/block/b-search/b-search.xsl"/> xsl:import href="../../common/block/b-search/input/b-search.input.xsl"/> xsl:import href="../../common/block/b-search/sample/b-search.sample.xsl"/> xsl:import href="../../common/block/b-search/precise/b-search.precise.xsl"/> xsl:import href="../../common/block/b-search/button/b-search.button.xsl"/> xsl:import href="../../common/block/b-head-userinfo/b-head-userinfo.xsl"/> xsl:import href="../../common/block/b-user/b-user.xsl"/> xsl:import href="../../common/block/b-head-userinfo/service/b-head-userinfo.service.xsl"/> xsl:import href="../../common/block/b-head-userinfo/setup/b-head-userinfo.setup.xsl"/> xsl:import href="../../common/block/b-head-userinfo/region/b-head-userinfo.region.xsl"/> xsl:stylesheet>
Мы перестали писать эти файлы руками, началась генерация кода.
Скорость селекторов (2009)
При реализации новой версии Яндекс.Почты была поставлена задача сделать ее быстрой.
Для решения задачи мы начали использовать XSL в браузере (и подгружать XML, необходимый для отрисовки данных на странице). Возникла проблема: трансформации отрабатывались быстро, но вставка в DOM полученного результата происходила очень медленно. При этом, отключение CSS решало проблему.
Выяснилось, что работу замедляют CSS-селекторы, которые при большом DOM-дереве и большой таблице стилей оказывают существенное влияние на скорость отрисовки браузером страницы.
Результаты исследования подробно описаны в статье.
Решение проблемы было уже готово — это абсолютно независимые блоки (АНБ).
Мы перевели все блоки в Лего на АНБ-нотацию и с тех пор создаем их так, чтобы у каждого DOM-узла был свой class , на который можно написать стили. Также мы не используем Tag Rules в CSS.
В классы элементов вносится имя блока, селекторы получаются простыми и быстрыми.
div class="b-head-logo"> span class="b-head-logo__name"> Авто span> div>
Стабилизация нотации
Постепенно мы пришли к тому, что нотация в коде и файловая структура устоялись и уже не меняются.
- В именах файлов разделитель . был заменен на __ . Пример b-block.elem.css —> b-block__elem.css Теперь они совпадают с CSS-селекторами.
- Были реализованы модификаторы у элементов по аналогии с модификаторами блоков: .b-block__elem_theme_green по аналогии с .b-block_theme_green .
- В имя файла модификатора и в его класс внесен тип модификатора. Было: .b-menu__item_current Стало: .b-menu__item_state_current Причина этого изменения — работа с модификаторами из JavaScript.
БЭМ и open source (2010)
В 2010 году мы снова вернулись к идее open source. Мы создали организацию bem на GitHub.
Библиотека bem-bl
Мы начали выносить блоки из Лего в bem-bl, проводя одновременно с этим рефакторинг.
Параллельно с переносом блоков в новую библиотеку публиковали информацию про них.
Инструменты
Для работы с файлами по БЭМ-методам нам понадобились свои инструменты. Началась реализация инструментов bem-tools на JavaScript под Node.js.
Уровни переопределения
Возникло новое понятие — уровень переопределения. Так мы стали называть директории с реализацией блоков.
Например, в проекте может быть:
- Публичная библиотека блоков с GitHub;
- Внутренняя библиотека lego;
- Блоки самого проекта.
Пример
bem-bl/ b-logo/ lego/ b-logo/ auto/ blocks/ b-logo/
На уровне переопределения можно задать другую схему именования директорий/файлов, отличную от нашей. Для этого нужно указать новый уровень в конфигурации:
.bem/ level.js
Например, вы можете задать другие разделители между именем блока и элемента, или не раскладывать все по директориям, а использовать плоскую структуру файлов.
Шаблонизатор BEMHTML
После экспериментов с разными шаблонизаторами, был разработан шаблонизатор BEMHTML, который позволяет:
- Писать шаблоны в БЭМ-терминах.
- Доопределять их на уровнях переопределения.
- Исполнять эти шаблоны как на сервере, так и в браузере, поскольку шаблоны компилируются в простой и быстрый JavaScript.
Видео по BEMHTML:
- bemhtml — bem js-шаблонизатор
- xjst — низкоуровневый js-шаблонизатор
- BEMHTML. Not yet another шаблонизатор
- BEMHTML. Not yet another шаблонизатор
- Шаблонизатор, работающий с несколькими уровнями
Резюме
Появлению БЭМ в том виде, что мы имеем сейчас, предшествовал долгий период проб и экспериментов.
Хочется обратить ваше внимание, что на всех этапах своего развития это всё же был БЭМ.
Тот БЭМ, что мы используем сейчас, — не единственное верное решение.
Мы рекомендуем использовать БЭМ в ваших проектах в том объеме, в котором он принесет наибольшую пользу. Можно пробовать применять его только для верстки. Мы сами начинали именно с этого. Гибкость БЭМ-методологии позволяет настраивать ее под свои текущие процессы и организовывать работу над проектом.
Главное понять, какие плюсы БЭМ принесет в ваш проект, выбрать подходящую для вас схему и начать применять у себя!
Если у вас возникнут вопросы, обязательно задавайте их на нашем форуме.