首页  >  文章  >  web前端  >  关于 BEM CSS 架构您需要了解的一切

关于 BEM CSS 架构您需要了解的一切

DDD
DDD原创
2024-10-02 06:12:02278浏览

我写这篇文章的目的是回答有关 BEM 的问题,这些问题通常不会被问到,但缺乏答案会影响架构的开发和理解。

本文不针对现在已经非常了解 CSS 和/或从未接触过 CSS 类命名约定的初学者。 如果您遇到这种情况,请跳至来源部分

标题改编自官方遗留 BEM CSS 网站

 

  • CSS 架构有什么用?
  • 什么是 BEM CSS?
    • 根据 UI 元素的耦合进行分类
    • 通过CSS表达组件关系
    • 复杂的组件关系
  • 使用 BEM 进行特异性控制
  • CSS 重用
  • BEM 和 Sass
  • 与其他架构的交互
  • 来源

? CSS 架构有什么用?

就像植物一样,代码的生长和繁荣方式取决于我们如何创建它、维护它以及事物的位置。

不同的植物对土壤、处理、水和阳光有不同的需求,就像不同的规范对标准、位置和管理有不同的需求一样。

CSS 是页面加载中的关键元素 - 在浏览器请求、下载所有 HTML 和 CSS 并将其转换为 DOM 和 CSSOM 并组装渲染树之前,渲染过程不会开始。 CSS 越少,性能就越好,CSS 越有组织、标准化和健壮,就越容易维护、管理、扩展和压缩。

CSS 选择器的命名方式会影响这些选择器的规则范围、特异性、位置和语义,从而加快或恶化开发过程。在多人编写 CSS 的项目中,我们的语言技能水平不同,习俗或个人标准也不同,如果不加以管理,可能会导致规则重复和错误过多。


?什么是 BEM CSS?

BEM 是一个专注于 styleguide 的 CSS 架构,它定义了分类和编写 CSS 规则的标准。 BEM 是在 Yandex 的应用程序上下文中创建的,该应用程序包含由一个或几个样式表管理的多个页面。

BEM 是 Block、Element、Modifier 的缩写。这些实体中的每一个都代表界面元素的类别,直接用 CSS 表示。

Tudo o que você precisa saber sobre a arquitetura BEM CSS

视觉上分离的界面组件的表示,来源:BEM CSS 官网

 


?根据 UI 元素的耦合度对其进行分类

一个Block代表一个独立的UI元素,它可以有子元素,就像标题有它的导航链接一样,这些子元素,如果它们是独立的,也可以是块。

界面组件独立性的概念可以通过以下公理来定义

“如果组件仅在特定上下文中有意义,则它必须是该上下文的元素。如果它可以存在于多个上下文中,则它必须是一个独立的块。”

元素是构成另一个较大元素并属于它的组件。如果此组件不存在并且不仅仅依赖于它所应用的上下文,那么它可以是一个。在BEM中,可以包含其他块,如果元素需要包含元素,那么它可能也是一个块。

块和元素之间的关系由双下划线block__element表示。

块和元素可能在同一上下文中包含美学或布局变化。为此,我们有修饰符。这些类可以表示同一组件的不同状态或变体。诸如元素之类的修饰符依赖于块并且仅从它派生。

块或元素与其修饰符之间的关系由双破折号 block--modifier 或 block__element--modifier 表示。

 

Referência Descrição
Tudo o que você precisa saber sobre a arquitetura BEM CSS Um checkbox é independente, pode ser aplicado dentro de outros componentes como
,
ou , por exemplo.
Tudo o que você precisa saber sobre a arquitetura BEM CSS Uma label pode ser considerada um bloco independente se ela for igualmente aplicada nos inputs da aplicação. Se a diferença entre labels for estética, ela pode ser um bloco que contém diversas variantes (modifiers), se a diferença entre as labels do input for estrutural (no template), faz sentido ela ser o elemento de um bloco
Tudo o que você precisa saber sobre a arquitetura BEM CSS Um card pode ser incluído em qualquer container, em diferentes contextos, podendo ser considerado independente (block). Se a imagem e textos dentro dos cards tiverem características que só faz sentido no contexto do card, elas podem ser consideradas elements
Tudo o que você precisa saber sobre a arquitetura BEM CSS Um botão pode ser administrado em qualquer lugar, inclusive mais de uma variante no mesmo lugar. Cada variante pode ser representada pela classe modificadora derivada do mesmo bloco ou elemento.

 

?透過CSS表達組件之間的關係

checkbox 為例,我們建構元件和定義其職責的方式會影響區塊、元素或修飾符。這個決策從模板開始:

<div class="form-field">
  <input
   class="form-field__input form-field__input--checkbox" 
   type="checkbox"
   value=""
   id="checkbox"
  />
  <label class="form-field__label" for="checkbox">
    Default checkbox
  </label>
</div>

在此範本中,我們將 .form-field 元件作為一個區塊,它將始終包含 .form-field__input 輸入和 .form-field__label 類別標籤。區塊(.form-field)和元素(標籤或輸入)之間的關係由雙下劃線表示。寫這篇文章意味著透過CSS我們了解到:

  • form-field 是一個獨立的實體,它的樣式工作不依賴於元件或父容器的上下文,任何表單都可以接收 .form-field 類型的欄位

  • 反過來,表單欄位可以包含 __input 和 __label,其佈局和外觀取決於表單欄位容器

.form-field {}
  .form-field__input {}
  .form-field__input--checkbox {}
  .form-field__label {}

 

當我們更改模板中的關係時,我們會更改 CSS 類別的拓撲。在下面的範例中,輸入標籤是單獨的實體:

<div class="column">
  <label class="label" for="checkbox">
    Default checkbox
  </label>
  <input
   class="input input--checkbox"
   type="checkbox"
   value=""
   id="checkbox"
  />
</div>
  • 在這種情況下,.column 既可以被視為一個區塊,也可以被視為一個實用程式類別。除了其他 CSS 架構之外,還可以將 BEM 與實用程式類別一起使用,包括那些遵循自己約定的類別。
  • .input 是一個引用所有輸入的區塊,可以在 .column 容器或任何其他容器中使用,無論 .label 是否存在都可以工作。它有一個 --checkbox 變體,可以載入複選框類型輸入的特定樣式,例如 :checked 的狀態。
  • .label 是一個區塊,其佈局和外觀不依賴輸入,甚至可以將 .input 作為子元素。

用 CSS 表達,它看起來像這樣:

.column {}

.input {}
.input--checkbox {}
.input--checkbox:checked {}

.label {}

 

BEM 注意元件的獨立性 - 更改元件在 HTML 中的位置不應影響其佈局,更改元件的 CSS 不應影響其他元件。當我們創建元素時,我們會展示一個列表,其中列出了更改其區塊時將受到影響的內容。透過這種方式,您可以了解什麼是組件及其部件以及完全獨立的模組。如遺留文件中所引用的:

2006 年,我們開始發展第一個大型專案:Yandex.Music 和 Ya.Ru。這些長達數十頁的項目揭示了目前開發方法的主要缺點:

  • 某一頁面程式碼的任何變更都會影響其他頁面的程式碼。
  • 選擇類別名稱很困難。

翻譯

2006 年,我們開始發展第一個大型專案:Yandex.Music 和 Ya.Ru。這些長達數十頁的項目暴露了目前開發方法的缺點:

  • 某一頁面程式碼的任何變更都會影響其他頁面。
  • 很難選擇如何命名類別。

 

?複雜的組件關係

2010 年,當BEM 開源並獲得網站時,每個UI 元素都是一個「組件」的想法甚至在原子設計(Brad Frost,2013)之前就已經存在,以應對保持一致性的挑戰不同頁面上的相同組件。例如,當 Yandex 建立 BEM 時,他們使用了多個 HTML 檔案以及僅一個 CSS 和一個 Javascript 檔案。

如果每個頁面都是一個區塊,且其元素依賴於該頁面的上下文,則頁面之間一致的所有程式碼都會重複。將 UI 分成小元件可以重複使用這些部分,無論它們在哪個頁面上使用。

為了舉例說明重用的概念,我們可以使用更複雜的元件,例如標頭:

Tudo o que você precisa saber sobre a arquitetura BEM CSS

這個標題可以寫成如下(短HTML):

<nav class="navbar">
  <div class="navbar__container">
    <a class="navbar__logo" href="#">Navbar</a>
    <button class="navbar__toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar__toggler-icon"></span>
    </button>
    <div class="navbar__collapse" id="navbarSupportedContent">
      <ul class="navbar__list">
        <li class="navbar__item">
          <a class="navbar__link active" aria-current="page" href="#">Home</a>
        </li>
        <!-- etc -->
        <li class="navbar__item navbar__dropdown">
          <a class="navbar__dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
            Dropdown
          </a>
          <ul class="navbar__dropdown-menu">
            <li>
              <a class="navbar__dropdown-item" href="#">
                Action
              </a>
            </li>
            <!-- etc -->
          </ul>
        </li>
        <!-- etc -->
      </ul>
      <form class="navbar__form" role="search">
        <input class="navbar__form-input" type="search" placeholder="Search" aria-label="Search">
        <button class="navbar__form-btn" type="submit">
          Search
        </button>
      </form>
    </div>
  </div>
</nav>

請注意,在此範例中,整個導覽列被宣告為一個大塊,並且它包含的所有內容都被視為 navbar元素。結果,我們失去了多次重用的機會。

Oportunidade Descrição
Tudo o que você precisa saber sobre a arquitetura BEM CSS O logotipo dentro de .navbar__logo é representado por um link contendo imagem ou texto. Existem diversos elementos que podem ser 'envelopados' em um link, como ícones back to top ou cards clicáveis. Esse código poderia pertencer a um bloco .link ou .media
Tudo o que você precisa saber sobre a arquitetura BEM CSS Os itens .navbar__list e .navbar__item nada mais são que uma lista desordenada no formato de linha ao invés de coluna. Isso poderia reutilizado representando-a pelo bloco .list com o modificador .list--row. O dropdown por sua vez pode ter o mesmo funcionamento fora do navbar, dentro do seu próprio bloco .dropdown, com seus elementos .dropdown__toggle e, como ele renderiza uma lista, podemos reutilizar .list e, se necessário, complementar com um modificador
Tudo o que você precisa saber sobre a arquitetura BEM CSS Esse componente de busca pode ser representado de forma mais "fechada" com um bloco .search e elementos .search__input, search__label e .search__trigger ou de forma mais "aberta" como .form-field, .input--text, .label e .button--outline

 

Ainda pensando nessa relação de componentes "abertos" e "fechados", podemos representar o componente de busca usado no último exemplo das duas forma usando CSS.

Componente de busca "fechado"
Os elementos input, label e button são acoplados ao bloco search, mudanças no componente ou no HTML de search influenciam nos seus elementos.

.search {}
  .search__input {}
  .search__label {}
  .search__button {}

Componente de busca "aberto"
Os elementos input, label e button são independentes do bloco de search, ele apenas organiza os blocos já existentes que compõe esse componente.

.form-field {}

.input {}
.input--text {}

.label {}

.button {}
.button--outline {}

? Controle da especificidade com BEM

Um dos problemas citados pela galera do Yandex é que mudanças no CSS podiam afetar partes indesejadas. No caso deles era utilizada uma folha de estilo pra todas as páginas da aplicação, e você pode achar que não é um problema pra ti se você usa algum processador como Sass ou diversos arquivos de CSS, mas é uma dor de cabeça ainda maior quando seletores de diferentes abrangências moram em múltiplos arquivos.

A forma que o BEM encontrou de contornar esse problema além da modularização de blocos é a especificidade. BEM só utiliza classes por motivos bem claros:

  • Classes podem ser reaproveitadas
  • Como você se refere diretamente aos elementos usando as classes, a especificidade tende a ser sempre muito próxima de 0,1,0
  • Pra alterar ou sobrescrever uma regra, basta adicionar outra classe
  • Regras que se referem à seletores mais globais como de resets ou normalizers são facilmente sobrescritas pelas classes.

Abaixo vemos um accordion que mantém uma diversidade de seletores, pra nos referirmos, por exemplo ao h2 desse accordion, temos que explicitamente dizer #accordion-item > h2:

<div id="accordion-item">
  <h2>
    <button class="btn">
      Accordion Item #1
    </button>
  </h2>
  <div class="collapse show">
    <div>
      <strong>This is the first item's accordion body.</strong>
    </div>
  </div>
</div>

 

Pra estilizar todos os elementos desse componente, precisamos estabelecer as seguintes relações:

/* 1,0,0 */
#accordion-item {}

/* 1,0,1 */
#accordion-item > h2 {}

/* 1,1,0 */
#accordion-item .btn {}

/* 1,1,0 */
#accordion-item .collapse {}

/* 1,2,0 */
#accordion-item > .collapse.show {}

/* 1,1,1 */
#accordion-item > .collapse > div {}

/* 1,1,2 */
#accordion-item > .collapse strong {}

Esse exemplo assume que .collapse por não se referir diretamente ao accordion (não é um .accordion-collapse, por exemplo) pode existir em outros lugares do código ou até em outros lugares de #accordion-item. Isso obriga a gente a deixar explícita sua relação com #accordion-item.

A regra #accordion-item > .collapse > div existe pois o estilo não pode impactar qualquer div, nem qualquer .collapse, pra alterar apenas aquilo que existe dentro do bloco #accordion-item precisamos adicionar muito mais complexidade e especificidade, além de que qualquer mudança no HTML desse componente causa bugs imprevisíveis.

<div class="accordion-item">
  <h2 class="title">
    <button class="button">
      Accordion Item #1
    </button>
  </h2>
  <div class="accordion-item__collapse">
    <div class="accordion-item__wrapper">
      <strong>This is the first item's accordion body.</strong>
    </div>
  </div>
</div>

No HTML acima estabelecemos as relações entre o .accordion-item e o .accordion-item__collapse sem adicionar especificidade. Ao nos referirmos diretamente aos elementos pela classe e não compondo seletores, quando alteramos o .accordion-item podemos quebrar o .accordion-item__collapse, mas ao alterar .accordion-item__collapse, o .accordion-item dificilmente será influenciado.

.title e .button serem blocos independentes faz com que alterarmos o HTML desse componente ou o CSS dessas classes não cause nenhum bug, pois elas existem de forma independente do bloco do accordion. Em relação à especificidade, a relação passa a ser da seguinte forma:

/* 0,1,0 */
.accordion-item {}

/* 0,1,0 */
.accordion-item__collapse {}

/* 0,1,0 */
.accordion-item__wrapper {}

/* 0,1,0 */
.title {}

/* 0,1,0 */
.button {}

 

A especificidade dessas regras é muito mais previsível, tanto que se eu quiser adicionar uma variante ao .accordion-item__wrapper basta eu criar uma classe com as regras e incluir no elemento no HTML, sem precisar criar no CSS um seletor .accordion-item__wrapper.accordion-item__wrapper--vertical.


? Reuso de CSS

Quando me refiro a reuso de CSS, não falo apenas de blocos que podem ser reaplicados em diferentes componentes, à nível de seletores, mas também à conjuntos de regras que realizam as mesmas ações. No exemplo abaixo, temos 3 componentes diferentes e mesmo se escritos na convenção do BEM haverá repetição de conjuntos de regras:

Elemento Regras
Tudo o que você precisa saber sobre a arquitetura BEM CSS
.alert {
  display: flex;
  gap: 0.5rem;
  /* etc... */
}
Tudo o que você precisa saber sobre a arquitetura BEM CSS
.nav {
  display: flex;
  gap: 1rem;
  /* etc... */
}
Tudo o que você precisa saber sobre a arquitetura BEM CSS
.breadcrumb {
  display: flex;
  gap: 0.5rem;
  /* etc... */
}

 

Percebe como todos os elementos que tem a disposição de 'linha' muito provavelmente terão um grupo de regras de display: flex o configura dessa forma, terão um gap: npx e provavelmente um flex-wrap: wrap quando fizer sentido pro layout 'quebrar' pra linha de baixo?

Usando o breadcrumb como exemplo, podemos criar um bloco que represente uma linha ou coluna genérica e que seja configurável à partir de variantes.

<ol class="breadcrumb">
  <li class="breadcrumb__item">Home</li>
  <li class="breadcrumb__item active">Library</li>
</ol>

Ao invés de repetir o conjunto de regras que configura uma coluna, podemos adicionar à classe .breadcrumb o bloco de .row. Não há restrições sobre o mesmo elemento ser representado por dois blocos distintos, pois o conceito de bloco é acoplado com seus elementos, não com componentes específicos.

/* Breadcrumb deixa de configurar o layout e passa apenas a implementar estilos estéticos */
.breadcrumb {
  font: 200 1rem Poppins;
  line-height: 1.45;
}

/* Row se torna responsável pelo layout */
.row {
  display: flex;
  gap: 1rem;
}

/* E pode ter variantes na convenção BEM caso conveniente */
.row--flexible {
  flex-wrap: wrap;
}

Nesse caso o gap de .row é estático, mas os elementos apresentados nos exemplos tem diferentes valores nessa propriedade, de quem é a responsabilidade? Se você possuí uma estrutura de CSS utilitário, a responsabilidade pode ser de .row que será configurada pelo CSS utilitário .gap-0.5 ou .gap-sm.

Se você não usa CSS utilitário, o gap pode ser configurado pelo próprio componente breadcrumb:

/* Ao implementar o gap com variáveis CSS temos um valor
default e um valor configurável */
.row {
  --row__gap-default: 1rem;
  display: flex;
  gap: var(--row__gap, var(--row__gap-default));
}

/* Dessa forma podemos configurar o gap pelo breadcrumb.
A row ainda não depende dele, mas caso ele seja aplicado com
uma row, ele a configura */
.breadcrumb {
  --row__gap: 0.5rem;
  font: 200 1rem Poppins;
  line-height: 1.45;
}

Separar os estilos de layout dos de aparência é uma prática do OOCSS (link do artigo na Smashing Magazine, em inglês). Categorizar o CSS em skin e structure vem do entendimento de que a estrutura e layout de um elemento ou componente é mais reaproveitável e ubíqua do que a aparência de um componente.

Usando a convenção BEM é possível criar blocos que se referem à estruturas e modificadores que se referem à aparência, sendo ela apenas uma convenção de escrita de classes, a categorização e separação dessas classes pode ficar na responsabilidade de outro tipo de arquitetura.


? BEM e Sass

Sass foi criado em 2009, um ano antes de BEM se tornar open source, a interação entre eles funciona extremamente bem (rsrs) por dois motivos:

  • Sass permite a criação de múltiplas folhas de estilo e a co-locação dos estilos de acordo com as necessidades do projeto,logo podemos criar uma folha pra cada bloco ou conjunto de blocos que se referem a um componente ou contexto.
  • Sass tem suporte à concatenação de string, o que torna ainda mais visual a relação da classe com seus elementos e modificadores. Pensando no exemplo do bloco .search citado anteriormente:
.search {
  &__input {}
  &__label {}
  &__button {}
}

 

Com CSS a relação de blocos e elementos se dá pelo prefixo do bloco, ex: .search__, no Sass os elementos são declarados dentro do bloco.

Um problema muito comum com Sass é o 'overnesting', que é quando aninhamos múltiplas classes em um bloco. Essa abordagem além de dificultar a legibilidade do bloco cria especificidade sem necessidade e acopla os elementos ao bloco mais do que o necessário.

/* Esse tipo de nesting ao invés de gerar seletores  0,1,0 */
.search {
  .search__item {}

  .search__label {
    &.search__label--floating {

    }
  }
}

/* Gera o CSS */
/* 0.2.0 */
.search .search__item {}

/* 0.2.0 */
.search .search__label {}

/* 0.3.0 */
.search .search__label.search__label--floating {}

O overnesting geralmente acontece pelo não conhecimento de como o Sass funciona ou pela tentativa de imitar o formato do HTML no CSS.


? Interações com outras arquiteturas

Como naming convention BEM é flexível quando nos apropriamos de outras arquiteturas ou apenas características delas. Como citado na documentação:

No matter what methodology you choose to use in your projects, you will benefit from the advantages of more structured CSS and UI. Some styles are less strict and more flexible, while others are easier to understand and adapt in a team.

Tradução

Não importa qual metodologia você use em seus projetos, vbocê vai se beneficiar das vantagens de uma UI e CSS mais estruturados. Alguns estilos são menos estritos e mais flexíveis, outros sãi fáceis de entender e adaptar em um time.

 

Podemos escrever as regras do tipo block do CUBECSS na convenção do BEM.

A diferença entre os blocos do CUBECSS e do BEM é o seu escopo, no CUBE há uma separação semântica entre estrutura, configuração e aparência. Após declarar a camada de Composition que defini layouts num nível mais macro (Como o object no OOCSS ou ITCSS) e criar classes utilitárias na camada de Utility, sobra pouca configuração a se fazer na cabada do Block, o deixando mais enxuto.

Podemos também criar as diversas camadas propostas pelo ITCSS e criar Objects, Components e Utilities na sintaxe do BEM. O ITCSS não define a forma que criamos modificadores e a relação dos componentes e seus estados/variantes, mas sim a taxonomia e localização dos elementos respeitando seu escopo e dando previsibilidade na sua especificidade e posição na cascata.

Podemos expressar as mesmas relações do SMACSS, por exemplo, declarando states quando esses alteram um módulo específico na sintaxe BEM:

/* Na convenção SMACSS */
.tab {
  background-color: purple;
  color: white;
}

.is-tab-active {
  background-color: white;
  color: black;
}

/* Na convenção BEM */
.tab {
  background-color: purple;
  color: white;
  &__is-active {
    background-color: white;
    color: black;
  }
}

States que não são específicos de um módulo podem ser declarados como classes utilitárias ou blocos independentes.

Há muitas outras formas de se escrever BEM, não necessariamente vinculadas a uma arquitetura, como no exemplo abaixo.


Afinal, BEM é a melhor arquitetura de CSS?

Não existe 'melhor arquitetura'. Todas as metodologias já propostas são claras nos problemas que elas visam resolver, e elas não necessariamente vão resolver todos os problemas da sua organização, time ou codebase. BEM pode resolver seus problemas de acoplamento de classes e especificidade, mas talvez não resolva o de performance ou localização.

O interessante é aumentar seu gabarito de metodologias, convenções e arquiteturas e escolher sua ferramenta de acordo com os problemas que tu visa resolver.

BEM num contexto de aplicação Vue com SFC pode ser incrível quando localizamos o bloco CSS no mesmo lugar que o componente que ele estiliza, mas pode gerar muito CSS duplicado ou inutilizado se não houver uma arquitetura mais voltada pra reuso de estruturas, como OOCSS ou CUBECSS.


? Fontes

  • Artigo BEM: guia definitivo do padrão CSS mais famoso de Tárcio Zemel (DPW), em português (Muito bom)
  • Vídeo BEM: A Convenção CSS Para Um Front End Muito Melhor de Tárcio Zemel (DPW), em português
  • Artigo An Introduction To Object Oriented CSS (OOCSS) na Smashing Magazine, em inglês
  • Artigo MindBEMding – getting your head ’round BEM syntax de Harry Roberts, em inglês
  • Artigo BEMIT: Taking the BEM Naming Convention a Step Further de Harry Roberts, em inglês
  • Artigo Side Effects in CSS de Phillip Walton, em inglês
  • Artigo BEM 101 em Smashing Magazine, em inglês

以上是关于 BEM CSS 架构您需要了解的一切的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn