FRTVT написал

Пере­использо­вать стили: БЭМ

Блоки, элементы, модификаторы... Что дальше?

Из чего состоит ваш сайт? Самый простой ответ на этот вопрос: из HTML-разметки, CSS-стилей и JavaScript-функционала. Вопрос заключается в том, как реализован ваш код.

Когда я только начинал верстать сайты, я делал как все новички: у меня было два-три файла, один с разметкой, другой со стилями... ну вы поняли. А дальше мой код выглядел примерно так: я начинал создавать новый элемент моего сайта, сразу же его стилизовал (иногда «в инлайне», иногда в файле стилей, что было ближе, чтобы не забыть), шёл дальше, доходил к следующему элементу и повторял упражнение.

Про переиспользование элементов речи даже не шло. Максимум, код просто копировался целым куском, а я шёл дальше. А когда мне надо было создать очередную страницу, я брал самый «свежий» вариант своего файла и тащил его дальше.

Стоит ли говорить, что тогда мне очень нравился Bootstrap и его готовые стили, которые было так легко повторять?

И тут я открыл БЭМ

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

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

Как выглядит код по БЭМ

Пример разметки:

<article class="article">
  <h1 class="article__header">Заголовок первого уровня</h1>
  <p class="article__text">
    Текст, который дан для примера.
  </p>
  <h2 class="article__header">Заголовок второго уровня</h2>
  <p class="article__text">
    Текст, который снова дан для примера.
  </p>
  <p class="article__text">
    Тут <span class="article__highlighting article__highlighting_red">важно</span>понять, что блоков может быть сколько угодно.
  </p>
</article>

Пример стилизации:

.article {
  padding: 1em 2em;
}

.article__header {
  font-weight: bold;
  color: #3B82F6;
}

.article__text {
  color: #4B5563;
}

.article__highlighting {
  font-style: italic;
}

.article__highlighting_red {
  color: #EF4444;
}

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

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

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

А ещё видно, что мы не пишем стили для тегов, а используем только классы. Это происходит потому что

  1. Код становится единообразным.
  2. Стили в классах начинают работать независимо друг от друга.
Почему так происходит?

Дело в том, что у каждого элемента в CSS есть свой вес.

По-умолчанию вес равен: 0, 0, 0, 0

Идентификатор меняет вес так: 0, 1, 0, 0

Класс — так: 0, 0, 1, 0

Тег и псевдоэлемент — так: 0, 0, 0, 1

Сходу запомнить что больше: идентификатор (0, 1, 0, 0) или тег — сложно (0, 0, 0, 1), к тому же их ограниченное количество на странице. Классов же может быть сколько угодно, они весят одинаково, поэтому всегда используйте классы. Это упростит чтение и понимание кода. Останется запомнить, что более приоритетен тот код, что написан ниже.

Немножко БЭМ-терминологии

Обратили внимание, что некоторые классы пишутся с подчёркиванием, а другие даже с двумя? Что это значит?

В БЭМ так принято обозначать различие разделов кода, которые являются независимыми, либо наоборот: не могут существовать без «родителя».

О блоках

Родительские элементы, которые можно использовать где угодно, называют «Блоками». Блок — это независимый элемент, который не теряет своей сути и всегда описывает своё состояние. Его обозначают главным словом в названии класса, например: .article.

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

Блок всегда остаётся собой, куда бы вы его ни поставили. У вас может быть много одинаковых блоков, которые ведут себя однообразно. Представьте себе две книги, пять полок, карточку товара на сайте интернет-магазине. Их может быть сколько угодно.

При этом есть очень важное правило: блок не влияет на окружение. Например, книге безразлично, как она стоит на полке, а полке без разницы, сколько полок в квартире.

Об элементах

Дочерние элементы не могут существовать без родительских. Например, глава книги будет совершенно бесполезна, без всех её остальных частей. Вы же не будете читать про приключения Буратино с того момента, как он встретил Артемона. Вы спросите: Что за Буратино? Что за Артемон? Что, вообще, тут происходит?

В БЭМ дочерние элементы всегда обозначают с указанием того, к какому родителю он принадлежит. Например: .article__header, .article__text. Согласитесь, что текст внутри «подвала» или «в шапке» может отличаться от текста основной статьи? Так вы сможете обозначить каждый из них и не ошибиться, где бы в коде вы ни находились.

А что, если элемент отличается?

Давайте предположим, что у вас в руках две книги, в каждой есть главы, буквы, какие-то персонажи. Свёрстаны они, приблизительно, одинаково, но одна с красной обложкой, а другой с синей. Как это обозначить в коде?

Для этого нужны модификаторы.

Предположим, что наша книга — это блок .book, тогда красную книгу мы можем обозначить как .book_red, а синюю .book_blue. Такие штуки называются модификатором блока, но их можно использовать и к элементу. Например, мы у нас может быть обычный заголовок .book__header, а может быть цветной — .book__header_colored.

Модификаторов у блока или элемента может быть любое количество. Например, у вас есть жирный шрифт (.font__decoration_bold) и курсивный (.font__decoration_italic), а может быть одновременно и жирным, и курсивным (.font__decoration_bold.font__decoration_italic).

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

/* Так плохо */
.title {
  font-weight: bold;
  color: red;
}

.title.title_green {
  color: green;
}

Так вы однажды запутаетесь и забудете, где и что обозначили, и смысл в БЭМ потеряется.

.title {
  font-weight: bold;
}

.title_red {
  color: red;
}

.title_green {
  color: green;
}

А как расставить элементы?

Вроде стало всё понятно, но если ты пишешь: «Блок не влияет на своё окружение», как сделать отступ у блока, например снизу, ведь добавить блоку margin-bottom: 1em; нельзя?

Правильно, нельзя. Потому что «книга» не знает, где она стоит на «полке». Зато об этом прекрасно знает полка. И тут нам на помощь приходят миксы или элементы с двумя классами.

<section class="rack">
  <article class="rack__book book"></article>
  <article class="rack__book book book_red"></article>
  <article class="rack__book book"></article>
</section>
<section class="table">
  <article class="table__book book"></article>
</section>
/* Описываем полку */
.rack {
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
}

/* Уточняем положение книг */
.rack__book:not(:last-of-type) {
  margin-right: 1em;
}

/* Описываем стол */
.table {
  padding: 2em;
}

/* Где там будет книга?*/
.table__book {
  margin: 0 auto;
}

/* И да, книги одинаковые */
.book {
  width: 200px;
  height: 100px;
}

/* Но одна красная */
.book_red {
  background: red;
}

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

И тут я закрыл БЭМ

Казалось бы, ну раз БЭМ — такая крутая штука, почему я не использую его везде и всегда? Например, даже в этой версии блога есть противоречащая БЭМу стилизация по селекторам.

Всё дело в том, что веб-разработчики не всегда пишут код полностью. Очень часто он генерируется движком, как в случае с 11ty.

Допустим, у вас задача: «Установить максимальную ширину всех без исключения картинок в 100%».

Что вы напишете?

img {
  max-width: 100%
}

Или

.container__img {
  max-width: 100%
}

в стилях, а ещё дополнительно создать файл трансформации, например:

/**
 * @param {Window} window
 */
module.exports = function (window) {
  window.document
    .querySelector('.container')
    ?.querySelectorAll('img')
    ?.forEach((image) => {
      image.classList.add('container__img')
    })
}

ну и не забыть в файле .eleventy.js что-то похожее на:

const imgTransform = require('./transforms/images-transform');

module.exports = function(eleventyConfig) {
  ...
  {
    const transforms = [
      ...
      imgTransform,
      ...
    ].filter(Boolean);

    eleventyConfig.addTransform('html-transforms', async (content, outputPath) => {
      if (outputPath && outputPath.endsWith('.html')) {
        const window = parseHTML(content);

        for (const transform of transforms) {
          await transform(window, content, outputPath);
        }

        return window.document.toString();
      }

      return content;
    })
  }
  ...
}

Как это сделано в платформе Доки, где команда не могла «забить» на такие вольности в обращении с БЭМ. Я же в личном блоге могу это сделать.

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

Если вы, как разработчик, знаете как у вас в проекте устроены селекторы и точно знаете, что такое исключение ничего не сломает, то не используйте БЭМ там, где он излишен.

БЭМ — это прописи с наклонной полосой. БЭМ классно учит разбираться в специфичности. Разобрались в этом. Снимайте боковые колёса с велосипеда и пишите крутой код.