Как CSS @scope может заменить БЭМ
Грядущая реализация @scope
в Chrome может ещё больше расширить преимущества БЭМ, позволив определять стили на уровне блоков в таблице стилей. Это может упростить поддержку стилей и обеспечить более жёсткий контроль над CSS-каскадом, влияющим на любое фронтенд-приложение.
В этой статье мы покажем, как использовать функцию @scope
в Chrome и как с её помощью заменить БЭМ во фронтенд-проектах. Мы рассмотрим несколько примеров, все из которых вы можете проверить на примерах из проекта на GitHub.
Что такое CSS @scope
В готовящемся к выпуску Chrome 118 функция @scope позволяет создавать диапазоны CSS стилей на уровне блоков. Это даёт разработчикам больше возможностей для управления CSS стилями, поскольку теперь мы можем специально определять область видимости для разделов представления непосредственно в CSS файлах.
Рассмотрим следующий пример HTML:
<main className="sample-page">
<h1>With Scope</h1>
<section className="first-section">
<p>some text</p>
<p>
some text and then a <a href="/">back link</a>
</p>
</section>
<section className="second-section">
<h2>Dog Picture</h2>
<div>
<p>second section paragraph text</p>
</div>
<img src={'./DOG_1.jpg'} alt="dog" />
</section>
</main>
В этом HTML мы можем стилизовать элементы в области стилей second-section
следующим образом:
.second-section {
display: flex;
flex-direction: column;
border: solid;
padding: 40px;
margin: 20px;
}
@scope (.second-section) {
h2 {
text-align: center;
}
img {
max-width: 400px;
max-height: 100%;
}
div {
display: flex;
justify-content: center;
margin: 20px;
}
p {
max-width: 200px;
text-align: center;
background-color: pink;
color: forestgreen;
padding: 10px;
border-radius: 20px;
font-size: 24px;
}
}
С помощью @scope
можно также создать область видимости "пончик" ("donut" scope), в которой начальная и конечная секции определяются для набора стилей и элементов в них. В том же HTML, приведённом выше, область видимости "пончик" может определять стили от начальной области sample-page
до места, где находится стилизованная область second-section
:
/* donut scope */
@scope (.sample-page) to (.second-section) {
p {
font-size: 24px;
background-color: forestgreen;
color: pink;
text-align: center;
padding: 10px;
}
a {
color: red;
font-size: 28px;
text-transform: uppercase;
}
}
Самое замечательное в этом то, что это функционирует очень похоже на то, что можно сделать с помощью стилизации БЭМ — но с меньшим количеством кода.
Поддержка браузерами CSS @scope
По состоянию на 2 октября 2023 года CSS @scope
ещё не выпущен, поэтому для его использования необходимо включить флаг экспериментальных веб-функций. Для этого сначала откройте вкладку в Chrome и перейдите по адресу chrome://flags/, затем найдите и включите флаг Experimental Web Platform features:
После установки экспериментального флага в Chrome добавление @scope
к таблицам стилей должно работать в любой сессии Chrome.
Что такое БЭМ
Метод стилизации Блок Элемент Модификатор (БЭМ) — это способ группировки стилей в рамках HTML-представления, по которому можно легко перемещаться.
Рассмотрим большую HTML-страницу, содержащую множество элементов с различными стилями. После задания нескольких начальных имён стилей может оказаться сложным поддерживать их при масштабировании страницы. БЭМ пытается решить эту проблему, структурируя имена стилей в соответствии с тем, что на самом деле является стилем.
Блок — это содержащий элемент HTML. Рассмотрим примерно такой HTML:
<main className="sample-page">
<h1 className="sample-page__title">With BEM</h1>
<section className="sample-page__first-section">
<p className="sample-page__first-section--first_line">
some text
</p>
<p className="sample-page__first-section--second-line">
some text and then a{' '}
<a
className="sample-page__first-section--second-line-link"
href="/"
>
back link
</a>
</p>
</section>
</main>
В этом HTML фрагменте:
- Блок: Стиль
sample-page
является блоком, поскольку он оборачивает группу элементов. - Элемент: При стилизации элемента
<h1>
, который рассматривается как элемент, к имени стиля добавляется дополнительный__
, создаваяsample-page__title
. То же самое можно сказать и оsample-page__first-section
. - Модификатор: При стилизации элемента
<p>
внутри элемента<section>
имя стиля имеет дополнительный--first-line
, создаваяsample-page__first-section--first-line
, таким образом:- Блок (1) —
sample-page
- Элемент (2) —
first-section
и - Модификатор (3) —
first-line
- Блок (1) —
БЭМ хорошо масштабируется, особенно если вы используете SASS для обёртывания стилей в группы с оператором &
, чтобы создать нечто подобное:
.sample-page {
display: flex;
flex-direction: column;
margin-top: 10px;
&__title {
font-size: 48px;
color: forestgreen;
}
&__first-section {
font-size: 24px;
border: solid;
padding: 40px;
margin: 20px;
&--first-line{
font-size: 24px;
background-color: forestgreen;
color: pink;
text-align: center;
padding: 10px;
}
}
}
Проблема заключается в том, что в очень большом проекте это приводит к появлению очень больших CSS- или SASS-файлов, управлять которыми в рамках масштаба может быть затруднительно. Можно заменить БЭМ стили на @scope
и сделать определения стилей меньше и более управляемыми. Мы продемонстрируем, как это работает, в следующем разделе.
Рефакторинг БЭМ для использования @scope
Лучше всего преимущества использования @scope
можно продемонстрировать на примере приложения с одним из ведущих фреймворков или библиотек, например React. В примере приложения на GitHub в папке react-example
есть проект, в котором страница сначала стилизована с помощью БЭМ, а затем подвергнута рефакторингу с использованием @scope
.
Вы можете запустить приложение и нажать на кнопки WithBEM или WithScope, чтобы увидеть конкретные реализации. Компоненты и таблицы стилей имеют соответствующие имена с префиксами WithBEM
или WithScope
в папках pages
и styles
соответственно.
Начиная с компонента WithBEMPage.tsx
, стилизованного под БЭМ, мы впервые видим HTML, стилизованный в рамках метода БЭМ:
<main className="sample-page">
<h1 className="sample-page__title">With BEM</h1>
<section className="sample-page__first-section">
<p className="sample-page__first-section--first_line">
some text
</p>
<p className="sample-page__first-section--second-line">
some text and then a{' '}
<a
className="sample-page__first-section--second-line-link"
href="/"
>
back link
</a>
</p>
</section>
<section className="sample-page__second-section">
<h2 className="sample-page__second-section--title">
Dog Picture
</h2>
<div className="sample-page__second-section--div">
<p className="sample-page__second-section--div-paragraph">
second section paragraph text
</p>
</div>
<img
className="sample-page__second-section--image"
src={'./DOG_1.jpg'}
alt="dog"
/>
</section>
</main>
В компоненте WithScopePage.tsx
мы видим, насколько чистым получился рефакторинг:
<main className="sample-page">
<h1>With Scope</h1>
<section className="first-section">
<p>some text</p>
<p>
some text and then a <a href="/">back link</a>
</p>
</section>
<section className="second-section">
<h2>Dog Picture</h2>
<div>
<p>second section paragraph text</p>
</div>
<img src={'./DOG_1.jpg'} alt="dog" />
</section>
</main>
Для рефакторинга из БЭМ в @scope
достаточно найти группы стилей, а затем соответствующим образом добавить в них свои стили. Сначала рассмотрим секцию заголовка. В исходном файле WithBEMPage.tsx
для каждой секции были определены свои стили. В версии @scope
имеется более лаконичное определение стилей для конкретных элементов:
.sample-page {
display: flex;
flex-direction: column;
margin-top: 10px;
}
/* заменён */
/* .sample-page__title {
font-size: 48px;
color: forestgreen;
} */
/* donut scope */
@scope (.sample-page) to (.first-section) {
h1 {
font-size: 48px;
color: forestgreen;
}
}
Аналогично, в первой секции контента исходные стили БЭМ выглядят следующим образом:
.sample-page__first-section {
font-size: 24px;
border: solid;
padding: 40px;
margin: 20px;
}
.sample-page__first-section--first_line {
font-size: 24px;
background-color: forestgreen;
color: pink;
text-align: center;
padding: 10px;
}
.sample-page__first-section--second-line {
font-size: 24px;
background-color: forestgreen;
color: pink;
text-align: center;
padding: 10px;
}
.sample-page__first-section--second-line-link {
color: red;
font-size: 28px;
text-transform: uppercase;
}
Рефакторинг этой первой секции с помощью @scope
позволил получить более лаконичное определение стиля:
.first-section {
font-size: 24px;
border: solid;
padding: 40px;
margin: 20px;
}
/* donut scope */
@scope (.sample-page) to (.second-section) {
p {
font-size: 24px;
background-color: forestgreen;
color: pink;
text-align: center;
padding: 10px;
}
a {
color: red;
font-size: 28px;
text-transform: uppercase;
}
}
Другим приятным побочным эффектом этого стало то, что вид HTML стал меньше и легче читается. Если рассматривать предыдущий вариант:
<section className="sample-page__first-section">
<p className="sample-page__first-section--first_line">
some text
</p>
<p className="sample-page__first-section--second-line">
some text and then a{' '}
<a
className="sample-page__first-section--second-line-link"
href="/"
>
back link
</a>
</p>
</section>
А потом:
<section className="first-section">
<p>some text</p>
<p>
some text and then a <a href="/">back link</a>
</p>
</section>
Пройдя по двум примерам компонентов, можно применить рефакторинг к каждой секции. В итоге можно отметить, что стилизация стала чище и легче для восприятия.
Дополнительные преимущества @scope
перед БЭМ
Помимо преимуществ рефакторинга БЭМ в @scope
, использование @scope
также позволяет лучше контролировать каскад CSS. Каскад CSS — это алгоритм, определяющий, как веб-браузеры обрабатывают условия стилизации элементов на сформированной HTML-странице.
При работе с любым фронтенд-проектом разработчикам приходится учитывать побочные эффекты от каскада, когда стили приводят к странным результатам. Используя @scope
, можно потенциально контролировать побочные эффекты каскада за счёт жёсткой привязки к элементам.
В примере GitHub проекта в папке html-css
находятся два базовых HTML-файла, содержащие пример страницы, на которую влияет каскад. Эти примеры были модифицированы на основе примеров, приведённых в статье Bram.us A Quick Introduction to CSS Scope
.
В файле no_scope.html
стили и несколько элементов определены следующим образом:
<!DOCTYPE html>
<html>
<head>
<title>Plain HTML</title>
<style>
.light {
background: #ccc;
}
.dark {
background: #333;
}
.light a {
color: red;
}
.dark a {
color: yellow;
}
div {
padding: 2rem;
}
div > div {
margin: 0 0 0 2rem;
}
p {
margin: 0 0 2rem 0;
}
</style>
</head>
<body>
<div class="light">
<p><a href="#">First Level</a></p>
<div class="dark">
<p><a href="#">Second Level</a></p>
<div class="light">
<p><a href="#">Third Level</a></p>
</div>
</div>
</div>
</body>
</html>
В результате получается следующее:
Проблема заключается в том, что в соответствии с заданным CSS Third Level
должен быть выделен красным цветом, а не жёлтым. Это побочный эффект CSS-каскада, поскольку стилизация страницы интерпретируется на основе порядка появления, и поэтому Third Level
воспринимается как жёлтый, а не красный. Взяв диаграмму из оригинального сообщения Bram.us, можно увидеть порядок, в котором каскад CSS оценивает селекторы и стили:
Без использования @scope
CSS каскад переходит от Специфичности непосредственно к Порядку Появления. При использовании @scope
CSS каскад в первую очередь учитывает элементы @scope
. Это можно увидеть, добавив @scope
специально для стилей .light
и .dark
в примере.
Во-первых, измените исходные HTML и CSS следующим образом:
<!DOCTYPE html>
<html>
<head>
<title>Plain HTML</title>
<style>
.light {
background: #ccc;
}
.dark {
background: #333;
}
div {
padding: 2rem;
}
div > div {
margin: 0 0 0 2rem;
}
p {
margin: 0 0 2rem 0;
}
@scope (.light) {
:scope {
background: white;
}
a {
color: red;
}
}
@scope (.dark) {
:scope {
background: black;
}
a {
color: yellow;
}
}
</style>
</head>
<body>
<div class="light">
<p><a href="#">First Level</a></p>
<div class="dark">
<p><a href="#">Second Level</a></p>
<div class="light">
<p><a href="#">Third Level</a></p>
</div>
</div>
</div>
</body>
</html>
В результате мы получаем вывод, который выглядит следующим образом:
Заключение
В этой статье мы рассмотрели способы рефакторинга приложений в БЭМ стиле для использования новой функции @scope
, появившейся в Chrome. Мы рассмотрели принцип работы @scope
, а затем переработали простую страницу из БЭМ в @scope
.
Новая функция @scope
потенциально может стать большим выигрышем для разработчиков фронтенда. Однако её поддержка должна быть реализована и в других браузерах, что может занять некоторое время. Пока же это, безусловно, интересная функция, которая может оказаться очень полезной для стилизации фронтенд-проектов.
Похожие статьи
- Ограничение области действия селекторов с помощью CSS правила @scope
- БЭМ vs SMACSS: Сравнение CSS методологий
- Написание более чистого CSS кода с помощью БЭМ