ABEM. Более полезная адаптация BEM CSS

ABEM. Более полезная адаптация BEM CSS

От автора: BEM CSS — популярное соглашение об именах классов CSS, упрощающее CSS. В этой статье предполагается, что вы уже знакомы с данным соглашением. Если нет, больше об этом можно найти на сайте getbem.com, чтобы получить базовые знания.

Стандартный синтаксис для BEM:

Лично я большой поклонник методологии соглашения об именах. Разделение стилей на небольшие компоненты намного легче поддерживать, чем иметь море высокой специфичности, распространившееся по всей таблице стилей. Однако есть несколько нюансов с синтаксисом, которые могут вызывать проблемы в процессе производства, а также создавать путаницу для разработчиков. Вместо этого я предпочитаю использовать слегка измененную версию синтаксиса. Я называю это ABEM (Atomic Block Element Modifier):

Префикс Atomic Design

A / m / o — префикс Atomic Design. Не следует путать с Atomic CSS, что совсем другое. Атомный дизайн — это метод организации компонентов, который максимизирует возможность повторного использования кода. Он разбивает компоненты на три папки: атомы, молекулы и организмы. Атомы — это суперпростые компоненты, которые обычно состоят из одного элемента (например, компонента button). Молекулы представляют собой небольшие группы элементов и / или компонентов (например, одно поле формы, показывающее метку и поле ввода). Организмы представляют собой крупные сложные компоненты, состоящие из множества компонентов молекул и атомов (например, полная форма регистрации).

ABEM. Более полезная адаптация BEM CSS

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

camelCase

То, что позволяет настраивать группировку

Классический BEM разделяет каждое отдельное слово в секции с помощью тире. Обратите внимание, что атомный префикс в приведенном выше примере также отделен тире от остальной части имени класса. Взгляните на то, что происходит, когда вы добавляете атомный префикс к BEM classic vs camelCase:

С первого взгляда имя компонента при чтении классического метода выглядит как «o subscribe form». Значение «о» полностью утрачено. Однако, когда вы применяете «o-» к версии camelCase, становится ясно, что это намеренно было записано как отдельный фрагмент информации для имени компонента.

Теперь вы можете применить атомный префикс к классическому BEM, используя «o» следующим образом:

Это позволило бы решить проблему «о», потерявшейся среди остальной части названия класса, однако не решит основной проблемы в классическом синтаксисе BEM. Разделяя слова тире, тире больше не доступно для использования в качестве механизма группировки. Использование camelCase освобождает вас от использования символа тире для дополнительной группировки, даже если эта группировка просто добавляет номер в конце имени класса.

Более быстрая обработка группировок

camelCase обладает дополнительным преимуществом, благодаря чему легче группировать имена классов. С camelCase каждый пробел, который есть в имени класса, представляет собой некоторую группировку. В классическом BEM каждый пробел может быть либо группировкой, либо пробелом между двумя словами в одной группе.

Взгляните на рисунок классического класса BEM (плюс атомный префикс) и попытайтесь выяснить, где начинаются и заканчиваются разделы префикса, блока, элемента и модификатора:

ABEM. Более полезная адаптация BEM CSS

А теперь попробуйте иначе. Это тот же самый класс, что и выше, за исключением того, что он использует camelCase для разделения каждого слова вместо тире:

ABEM. Более полезная адаптация BEM CSS

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

классический BEM + атомный префикс

ABEM. Более полезная адаптация BEM CSS

camelCase BEM + атомный префикс

ABEM. Более полезная адаптация BEM CSS

Использование многоклассового селектора (ответственно)

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

«Но это приводит к более высокой специфичности! Разве вы не знаете, что специфичность по своей сути — зло?»

Специфичность! = Плохо.

Неконтролируемая одичавшая специфичность = bad.

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

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

Разделение модификатора приводит к более чистому HTML

Это самое большое изменение в синтаксисе, который вводит ABEM. Вместо того, чтобы подключать модификатор к классу элемента, он применяется как отдельный класс.

Одна из вещей, на которые практически каждый жалуется, когда они впервые начинают изучать BEM, — это уродство. Это особенно плохо, когда речь идет о модификаторах. Взгляните на это злодеяние. У него есть только три модификатора, и все же он выглядит как крушение поезда:

B__E — М:

Посмотрите на все эти повторения! Они затрудняют чтение того, что он на самом деле пытается сделать. Теперь рассмотрим пример ABEM, который имеет все те же модификаторы, что и предыдущий пример:

A-B__E -M:

Гораздо чище, не так ли? Гораздо легче увидеть, что пытаются сказать те классы-модификаторы, не мешая этим повторяющимся мусором.

При проверке элемента с браузером DevTools вы все еще видите полное правило на панели стилизации, чтобы таким образом сохранить соединение с исходным компонентом:

Это не сильно отличается от эквивалента BEM

Управление состоянием становится простым

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

Изменим цвета фона заголовка раздела

Отобразим области содержимого

Сосредоточим стрелку внизу

Будем в этом примере придерживаться классического синтаксиса B__E-M и правила одного класса для CSS-селектора. Это то, чем мы заканчиваем (обратите внимание, что для краткости этот аккордеон недоступен):

SCSS выглядит довольно чистым, но взгляните на все дополнительные классы, которые мы должны добавить в HTML только для одного изменения состояния!

HTML, в то время как сегмент закрывается с использованием BEM:

HTML, в то время как сегмент открывается с использованием BEM:

Теперь давайте посмотрим, что произойдет, когда мы перейдем к использованию этого нового метода A-B__E -M:

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

HTML, когда сегмент открыт с использованием ABEM:

Кроме того, посмотрите, насколько проще стал javascript. Я написал JavaScript так, как мог, и это было результатом:

JavaScript при использовании чистого BEM:

JavaScript при использовании ABEM:

Это был просто простой пример аккордеона. Подумайте о том, что происходит, когда вы экстраполируете его на что-то вроде липкого заголовка, который меняется, когда он липнет. Липкий заголовок может потребовать указать 5 различных компонентов, затем в каждом из этих 5 компонентов 5 элементов, возможно, потребуют отреагировать на то, что этот заголовок липнет. Это 25 element.classList.add(«[componentName]__[elementName]—sticky»)правил, которые нам нужно написать в js, чтобы строго придерживаться соглашения об именах BEM. В чём смысл? 25 уникальных классов, которые добавляются к каждому затронутому элементу или только один sticky- класс, добавленный в заголовок, который позволяет легко и просто прочитать все 5 элементов из всех 5 компонентов?

«Решение» BEM совершенно нецелесообразно. Применение стилизации модификатора к крупным сложным компонентам заканчивается превращением в серую область. Серая область, которая вызывает путаницу у любых разработчиков, пытающихся строго придерживаться соглашения об именах BEM.

Проблемы с модификатором ABEM

Разделение модификатора не лишено недостатков. Однако есть несколько простых способов их обойти.

Проблема 1: Вложение

Итак, у нас есть наш аккордеон, и все работает отлично. Позже по линии клиент хочет вставить второй аккордеон в первый. Итак, вы берёте и делаете … но происходит это:

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

Это то, чего вы, очевидно, не хотите. Однако есть хороший способ избежать этого.

Чтобы объяснить это, давайте немного поиграем. Предполагая, что оба этих правила CSS активны в одном и том же элементе, какой цвет, по вашему мнению, будет иметь фон элемента?

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

Забавный факт: *самый низкий селектор специфичности в CSS. Это в основном означает «что-либо» в CSS. На самом деле он не имеет никакой специфики, то есть он не добавляет никакой специфики к селектору, в который вы его добавили. Это означает, что даже если вы использовали правило, состоящее из одного класса и 5 звезд ( .element > * > * > * > * > *), его все равно можно было бы легко перезаписать только одним классом в следующей строке CSS!

Мы можем воспользоваться этой маленькой причудой CSS, чтобы создать более целенаправленный подход к коду SCSS аккордеона. Это позволит нам безопасно встраивать наши аккордеоны.

Используя .-modifierName > * > &шаблон, вы можете нацелить прямых потомков с несколькими уровнями глубины, не вызывая зависания вашей специфики.

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

Проблема 2: Наименование коллизий

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

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

Черточка в начале имени по-прежнему означает, что это класс модификатора. Имя компонента предотвращает столкновения пространства имен с другими компонентами, которые не должны быть затронуты. Двойная тире в основном просто кивает классическому синтаксису модификатора BEM, чтобы удвоить усиление, что это класс модификатора.

Ниже приведен пример аккордеона и табуляции, но на этот раз с применением исправления пространства имен:

Я не рекомендую использовать этот метод по умолчанию, в основном для того, чтобы держать HTML в чистоте, а также для предотвращения путаницы, когда несколько компонентов должны использовать один и тот же модификатор.

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

Методика модификатора ABEМ

Чтобы наилучшим образом использовать модификатор ABEM, используйте синтаксис .-modifierName & или &.-modifierName по умолчанию (зависит от того, какой элемент имеет класс на нем)

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

Используйте имя компонента в модификаторе, если вы столкнулись с конфликтами совместно используемых имён модификаторов. Только делайте это, если вы не можете придумать другое имя модификатора, которое всё ещё имеет смысл.

Контекстно-зависимые стили

Еще одна проблема со строгим соблюдением BEM по одному классу по методологии выбора заключается в том, что она не позволяет писать стили, чувствительные к контексту.

Контекстно-зависимые стили в основном это- «если данный элемент внутри родителя, примените к нему определённые стили».

С контекстно-зависимыми стилями существует родительский компонент и дочерний компонент. Родительский компонент должен быть тем, который применяет стили, связанные с раскладкой, такие как margin и позиция для дочернего компонента ( .parent .child { margin: 20px }). Дочерний компонент по умолчанию никогда не должен иметь поля вокруг внешней части компонента. Это позволяет использовать дочерние компоненты в большом количестве контекстов, поскольку он является родителем, ответственным за его собственный макет, а не его дочерние элементы.

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

Чтобы углубиться в эту концепцию, давайте притворимся, что мы строим абсолютно новый веб-сайт, и сейчас мы создаем компонент формы подписки для него.

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

Спустя некоторое время начинаем кросс-браузерное тестирование. Мы открываем IE11 только для того, чтобы увидеть уродливую вещь, которая смотрит прямо на нас:

ABEM. Более полезная адаптация BEM CSS

IE11 выполняет некоторую поддержку CSS-сетки, но не поддерживает grid-gap или автоматическое размещение. После того, как некоторые слабаки ругаются и хотят, чтобы люди обновили свои браузеры, вы настраиваете стили, чтобы они выглядели следующим образом:

Теперь он выглядит идеально в IE. И с миром всё в порядке. Что же могло пойти не так?

Через пару часов поместите этот компонент button в другой компонент на сайте. Этот другой компонент также использует css-grid для компоновки своих дочерних элементов.

Теперь пишете следующий код:

Предполагается, что мы увидим макет, который выглядит вот так даже в IE11:

ABEM. Более полезная адаптация BEM CSS

Но вместо этого, из-за grid-column: 3; код, который был написан ранее, выглядит следующим образом:

ABEM. Более полезная адаптация BEM CSS

Итак, что мы делаем с этим grid-column: 3;CSS, который мы написали ранее? Нам нужно ограничить его родительским компонентом, но как?

Классический метод BEM, связанный с этим, заключается в том, чтобы добавить новый элемент класса родительских компонентов button:

На первый взгляд это решение выглядит довольно хорошо:

Он сохраняет определенность низкой

Родительский компонент контролирует свой собственный макет

Стилизация вряд ли будет кровоточить в другие компоненты, которые мы не хотим, чтобы они истекали кровью

Все потрясающе, и все в порядке с миром … верно?

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

Мне нравится использовать Pug для создания шаблонов страниц. В качестве примера я покажу вам, что я имею в виду под использованием Pug mixins.

Во-первых, вот оригинальный недружественный код IE, переписанный в формате mixin:

Теперь добавим к этому subscribe-form__submit классу IE 11:

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

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

Контейнерные запросы сделают это менее проблематичным, но они существуют ни в каждом браузере

Проблемы вокруг синтаксиса модификатора BEM воспитывают их уродливые головы.

Теперь попробуйте сделать это снова, но на этот раз с использованием контекстно-зависимых стилей:

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

Возможно, вы заметили, что в последнем примере я использовал селектор, который был тремя классами. Он был использован для применения 100% ширины к кнопке. Да, селектор трех классов это верное решение, если вы можете его аргументировать.

Я не хотел, чтобы 100% ширина применялась к кнопке каждый раз, когда она:

используется везде

помещается внутри формы подписки

помещена внутри боковой панели

Я хотел использовать ширину 100%, только когда она была внутри формы подписки или внутри боковой панели. Лучшим способом справиться с этим был с селектором трех классов.

На самом деле, я бы скорее использовал класс verticalStack-модификатора стиля ABEM для subscribe-form элемента, чтобы применить стили вертикального стека, или, возможно, даже выполнить его через запросы элементов с помощью EQCSS. Это означало бы, что я могу применять вертикальные стили стека в большем количестве ситуаций, чем, когда он находится на боковой панели. Однако для примера я сделал как контекстно-зависимые стили.

Теперь, когда мы понимаем контекстно-зависимые стили, вернемся к тому оригинальному примеру, который у меня был, и используем некоторые контекстно-зависимые стили для применения того самого grid-column: 3правила:

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

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

Любая связанная с CSS сетка, которая применяется к дочернему элементу ( grid-columnи grid-rowт. д.),

Любое связанное с Flexbox, которое применяется к дочернему элементу ( flex-grow, flex-shrink и align-self т. д.),

margin значения больше 0

positionкроме значения relative(наряду с top, left, bottom и right свойств)

transform если он используется для позиционирования translateY

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

width

height

padding

border

Чтобы всё окончательно стало ясно, контекстно-зависимые стили не гнездятся ради гнездования. Нужно думать о них, как будто это if заявление в JavaScript.

Итак, для правила CSS:

Думайте об этом, как будто пишете следующую логику:

Также понимайте, что написание такого правила, как это, имеет три уровня:

Следует думать, что вы пишете логику следующим образом:

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

И снова, еще раз, просто чтобы было понятно, гнездятся не ради гнездования!

Подведение итогов

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

Официальный синтаксис BEM:

Не поддерживает Atomic Design

Трудно продливается

Занимает больше времени, чтобы ваш ум обрабатывал группировку имен классов

Является ужасно некомпетентным, когда дело касается управления состоянием на больших компонентах

Пытается побудить вас использовать отдельные селекторы классов, когда двойные селекторы классов приводят к упрощению обслуживания

Пытается дать пространство имён всем, даже если это вызывает больше проблем, чем решает.

Делает HTML чрезвычайно раздутым, когда он сделан правильно

Неофициальный ABEM:

Упрощает работу с Atomic Design

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

Позволяет вашему разуму быстрее обрабатывать группировку имен классов

Отлично подходит для управления состоянием любого компонента, независимо от того, сколько компонентов

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

Устраняет пространство имен, когда оно не требуется

Сохраняет HTML достаточно чистым с минимальными дополнительными классами, применяемыми к модулям, сохраняя при этом все преимущества BEM

Отказ от ответственности

Это не я придумал -modifier (единственная тире перед именем модификатора) идею. Я обнаружил её в 2016 году в одной статье и не помню, кто изначально эту идею концептуализировал. Но я буду рад помочь им, если кто-нибудь знает статью.

Автор: Daniel Tonon

Источник: //css-tricks.com/

Редакция: Команда webformyself.

Метки:

Похожие статьи:

Комментарии Вконтакте: