ABEM:BEM 的一种更有用的改进版。

Avatar of Daniel Tonon
Daniel Tonon 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

BEM(块元素修饰符)是一种流行的 CSS 类命名约定,它使 CSS 更易于维护。本文假设您已经熟悉此命名约定。如果您不熟悉,可以访问 getbem.com 了解更多信息,了解基本知识。

BEM 的标准语法是

block-name__element-name--modifier-name

我个人非常喜欢这种命名约定背后的方法论。将样式分解成小的组件比在整个样式表中使用大量高特异性的样式更容易维护。但是,我对语法有一些问题,这些问题可能会导致生产问题,并给开发人员带来困惑。我更喜欢使用稍微调整后的语法版本。我称之为 ABEM(原子块元素修饰符)

[a/m/o]-blockName__elementName -modifierName

原子设计前缀

a/m/o 是一个 原子设计 前缀。不要与 原子 CSS 混淆,后者是完全不同的东西。原子设计是一种组织组件的方法,它最大限度地提高了代码重用能力。它将组件分成三个文件夹:原子、分子和有机体。原子是超简单的组件,通常仅由单个元素组成(例如,按钮组件)。分子是元素和/或组件的小组(例如,显示标签和输入字段的单个表单字段)。有机体是由许多分子和原子组件组成的复杂组件(例如,完整的注册表单)。

An Atomic Design Diagram showing atoms inside molecules inside an organism

使用经典 BEM 进行原子设计时遇到的困难在于,没有指示器表明块是什么类型的组件。这可能难以知道该组件的代码在哪里,因为您可能需要在 3 个单独的文件夹中搜索才能找到它。在开头添加原子前缀可以立即清楚地表明该组件存储在哪个文件夹中。

驼峰式命名法

允许自定义分组

经典 BEM 使用单个连字符分隔每个部分中的各个单词。请注意,上面示例中的原子前缀也通过连字符与类名的其余部分分隔。请查看现在将原子前缀添加到经典 BEM 与驼峰式命名法时会发生什么情况。

/* classic + atomic prefix */
.o-subscribe-form__field-item {}

/* camelCase + atomic prefix */
.o-subscribeForm__fieldItem {}

乍一看,使用经典方法读取时,组件名称看起来像是“o subscribe form”。“o”的意义完全丢失了。但是,当您将“o-”应用于驼峰式命名法版本时,很明显它是故意将其写成与组件名称分开的独立信息。

现在,您可以通过将“o”大写来将原子前缀应用于经典 BEM,如下所示:

/* classic + capitalized atomic prefix */
.O-subscribe-form__field-item {}

这将解决“o”在类名其余部分中丢失的问题,但它没有解决经典 BEM 语法中的核心底层问题。通过使用连字符分隔单词,连字符不再可用作分组机制。通过使用驼峰式命名法,它使您可以自由地使用连字符进行其他分组,即使该分组只是在类名末尾添加一个数字。

您的思维将更快地处理分组

驼峰式命名法还有一个额外的好处,那就是使类名的分组更容易在心理上处理。使用驼峰式命名法,您在类名中看到的每个间隙都表示某种分组。在经典 BEM 中,每个间隙可能分组同一组中两个单词之间的空格。

请查看经典 BEM 类(加上原子前缀)的轮廓,并尝试找出前缀、块、元素和修饰符部分的开始和结束位置。

classic BEM silhouette

好的,现在试试这个。它与上面的类完全相同,只是这次它使用驼峰式命名法而不是连字符来分隔每个单词。

camel case BEM silhouette

是不是容易多了?这些轮廓本质上是您在扫描代码时脑海中看到的。类名中所有这些额外的连字符使分组变得不那么清晰。当您阅读代码时,您的大脑会尝试处理它遇到的间隙是新的分组还是仅仅是新单词。这种缺乏清晰度会导致认知负荷在您工作时加重您的思维。

经典 BEM + 原子前缀
classic BEM silhouette revealed
驼峰式命名法 BEM + 原子前缀
camel case BEM silhouette revealed

谨慎使用多类选择器

BEM 的黄金法则之一是每个选择器只应该包含一个类。其理念是通过保持选择器的特异性较低且易于管理来保持 CSS 的可维护性。一方面,我同意低特异性比特异性泛滥更可取。另一方面,我强烈不同意每个选择器严格使用一个类的规则是项目中最好的选择。在您的样式中使用一些多类选择器实际上可以提高可维护性,而不是降低它。

“但是它会导致更高的特异性!难道你不知道特异性本质上是邪恶的吗??”

特异性 != 坏。

不受控制的特异性失控 = 坏。

拥有某些较高特异性的声明并不立即意味着您的 CSS 难以维护。如果以正确的方式使用,赋予某些规则更高的特异性实际上可以使 CSS 更易于维护。编写具有不均匀特异性的可维护 CSS 的关键是有目的地添加特异性,而不仅仅是因为列表项碰巧位于列表元素内部。

此外,难道我们不希望我们的修饰符样式比默认样式对元素具有更大的控制力吗?为了保持修饰符样式与普通样式具有相同的特异性级别而反其道而行之,在我看来很愚蠢。您什么时候真正希望您的常规默认样式覆盖您专门指定的修饰符样式?

分离修饰符可以使 HTML 更简洁

这是 ABEM 引入的语法方面最大的变化。您不是将修饰符连接到元素类,而是将其作为单独的类应用。

当人们第一次开始学习 BEM 时,几乎每个人都会抱怨它有多丑。在涉及修饰符时尤其如此。请查看这种暴行。它只应用了三个修饰符,但看起来却像一场火车事故。

B__E–M
<button class="block-name__element-name block-name__element-name--small block-name__element-name--green block-name__element-name--active">
  Submit
</button>

看看所有这些重复!这种重复使得很难阅读它实际上想要做什么。现在,请查看此 ABEM 示例,它与上一个示例具有相同的修饰符。

A-B__E -M
<button class="a-blockName__elementName -small -green -active">
  Submit
</button>

是不是干净多了?去掉所有重复的冗余信息后,更容易理解这些修饰符类想要表达的意思。

在使用浏览器开发者工具检查元素时,你仍然可以在样式面板中看到完整的规则,因此它以这种方式保留了与原始组件的连接。

.a-blockName__elementName.-green {
  background: green;
  color: white;
}

它与BEM等价物没有太大区别。

.block-name__element-name--green {
  background: green;
  color: white;
}

管理状态变得容易

ABEM相较于经典BEM的一个巨大优势在于,它使得管理组件状态变得异常容易。让我们以一个基本的折叠面板为例。当折叠面板的某个部分打开时,假设我们希望对样式进行以下更改:

  • 更改部分标题的背景颜色。
  • 显示内容区域。
  • 将向下箭头改为向上箭头。

在本例中,我们将坚持使用经典的B__E–M语法,并严格遵守每个CSS选择器一个类的规则。最终得到的结果如下(注意,为了简洁起见,此折叠面板不可访问)。

查看CodePen上的示例Accordion 1 – Pure BEM,作者是Daniel Tonon (@daniel-tonon),发布在CodePen上。

SCSS看起来很简洁,但请查看一下,仅仅为了改变一个状态,我们在HTML中需要添加多少额外的类!

使用BEM时HTML片段处于关闭状态

<div class="revealer accordion__section">
  <div class="revealer__trigger">
    <h2 class="revealer__heading">Three</h2>
    <div class="revealer__icon"></div>
  </div>
  <div class="revealer__content">
    Lorem ipsum dolor sit amet...
  </div>
</div>

使用BEM时HTML片段处于打开状态

<div class="revealer accordion__section">
  <div class="revealer__trigger revealer__trigger--open">
    <h2 class="revealer__heading">One</h2>
    <div class="revealer__icon revealer__icon--open"></div>
  </div>
  <div class="revealer__content revealer__content--open">
    Lorem ipsum dolor sit amet...
  </div>
</div>

现在让我们看看当我们切换到使用这种新奇的A-B__E -M方法时会发生什么。

查看CodePen上的示例Accordion 2 – ABEM alternative,作者是Daniel Tonon (@daniel-tonon),发布在CodePen上。

现在,单个类控制整个组件的状态特定样式,而不是必须分别对每个元素应用单独的类。

使用ABEM时HTML片段处于打开状态

<div class="m-revealer o-accordion__section -open">
  <div class="m-revealer__trigger">
    <h2 class="m-revealer__heading">One</h2>
    <div class="m-revealer__icon"></div>
  </div>
  <div class="m-revealer__content">
    Lorem ipsum dolor sit amet...
  </div>
</div>

此外,请注意JavaScript变得多么简单。我尽可能简洁地编写了JavaScript,结果如下:

使用纯BEM时的JavaScript代码

class revealer {
  constructor(el){
    Object.assign(this, {
      $wrapper: el,
      targets: ['trigger', 'icon', 'content'],
      isOpen: false,
    });
    this.gather_elements();
    this.$trigger.onclick = ()=> this.toggle();
  }

  gather_elements(){
    const keys = this.targets.map(selector => `$${selector}`);
    const elements = this.targets.map(selector => {
      return this.$wrapper.querySelector(`.revealer__${selector}`);
    });
    let elObject = {};
    keys.forEach((key, i) => {
      elObject[key] = elements[i];
    });
    Object.assign(this,	elObject);
  }
  
  toggle(){
    if (this.isOpen) {
      this.close();
    } else {
      this.open();
    }
  }

  open(){
    this.targets.forEach(target => {
      this[`$${target}`].classList.add(`revealer__${target}--open`);
    })
    this.isOpen = true;
  }

  close(){
    this.targets.forEach(target => {
      this[`$${target}`].classList.remove(`revealer__${target}--open`);
    })
    this.isOpen = false;
  }
}

document.querySelectorAll('.revealer').forEach(el => {
  new revealer(el);
})

使用ABEM时的JavaScript代码

class revealer {
  constructor(el){
    Object.assign(this, {
      $wrapper: el,
      isOpen: false,
    });
    this.$trigger = this.$wrapper.querySelector('.m-revealer__trigger');
    this.$trigger.onclick = ()=> this.toggle();
  }
  
  toggle(){
    if (this.isOpen) {
      this.close();
    } else {
      this.open();
    }
  }

  open(){
    this.$wrapper.classList.add(`-open`);
    this.isOpen = true;
  }

  close(){
    this.$wrapper.classList.remove(`-open`);
    this.isOpen = false;
  }
}

document.querySelectorAll('.m-revealer').forEach(el => {
  new revealer(el);
})

这只是一个非常简单的折叠面板示例。想象一下,当您将其扩展到诸如粘性头部(在粘性时会发生变化)之类的场景时会发生什么。粘性头部可能需要在头部变为粘性时通知5个不同的组件。然后,在每个组件中,可能有5个元素需要对头部变为粘性做出反应。这意味着我们需要在js中编写25个element.classList.add("[componentName]__[elementName]--sticky")规则才能严格遵守BEM命名约定。哪种方法更有意义?是向每个受影响的元素添加25个唯一的类,还是只向头部添加一个-sticky类,所有5个组件中的5个元素都能够轻松访问和读取?

BEM的“解决方案”完全不切实际。将修饰符样式应用于大型复杂组件最终会变成一个灰色地带。这个灰色地带会让任何试图尽可能严格遵守BEM命名约定的开发者感到困惑。

ABEM修饰符问题

分离修饰符并非没有缺点。但是,有一些简单的方法可以解决这些缺点。

问题1:嵌套

所以我们有了折叠面板,并且一切正常。稍后,客户希望在第一个折叠面板内嵌套第二个折叠面板。所以你继续这样做……然后发生了这种情况。

查看CodePen上的示例Accordion 3 – ABEM nesting bug,作者是Daniel Tonon (@daniel-tonon),发布在CodePen上。

在第一个折叠面板内嵌套第二个折叠面板会导致一个相当棘手的问题。打开父折叠面板也会将打开状态的样式应用于该片段中所有子折叠面板。

这显然是不希望发生的。但是,有一个很好的方法可以避免这种情况。

为了解释清楚,让我们玩一个小游戏。假设这两个CSS规则都作用于同一个元素,您认为该元素的背景色会是什么颜色?

.-green > * > * > * > * > * > .element {
  background: green;
}

.element.-blue {
  background: blue;
}

如果您说由于第一个规则的特殊性高于第二个规则,所以是绿色,那么您实际上是错的。它的背景色将是蓝色。

有趣的事实:*是CSS中最低的特殊性选择器。它基本上表示CSS中的“任何东西”。它实际上没有特殊性,这意味着它不会为添加到其中的选择器增加任何特殊性。这意味着,即使您使用了一个包含单个类和5个星号的规则(.element > * > * > * > * > *),它仍然可以被下一行CSS中的单个类轻松覆盖!

我们可以利用这个CSS的小技巧来为折叠面板的SCSS代码创建更具针对性的方法。这将允许我们安全地嵌套折叠面板。

查看CodePen上的示例Accordion 4 – ABEM nesting bug fix,作者是Daniel Tonon (@daniel-tonon),发布在CodePen上。

通过使用.-modifierName > * > &模式,您可以定位多层深的直接子元素,而不会导致特殊性失控。

我仅在必要时才使用这种直接定位技术。默认情况下,当我编写ABEM时,我会像在最初的ABEM折叠面板示例中那样编写。在大多数情况下,非目标方法通常就足够了。目标方法的问题在于,在某些内容周围添加单个包装器可能会破坏整个系统。非目标方法不会遇到此问题。它更加宽松,并且可以防止在以后需要修改HTML时样式中断。

问题2:命名冲突

使用非目标修饰符技术时可能会遇到一个问题,那就是命名冲突。假设您需要创建一组选项卡,并且每个选项卡中都有一个折叠面板。在编写此代码时,您使折叠面板和选项卡都对-active类做出响应。这会导致命名冲突。活动选项卡中的所有折叠面板都将应用其活动样式。这是因为所有折叠面板都是选项卡容器元素的子元素。选项卡容器元素才是实际应用了-active类的元素。(以下示例中的选项卡和折叠面板均不可访问,以简洁起见)。

查看CodePen上的示例Accordion in tabs 1 – broken,作者是Daniel Tonon (@daniel-tonon),发布在CodePen上。

解决此冲突的一种方法是简单地更改手风琴,使其响应-open类而不是-active类。我实际上建议采用这种方法。不过,为了举例说明,假设这不是一种选择。您可以使用上面提到的直接目标技术,但这会使您的样式非常脆弱。相反,您可以这样做,在修饰符的前面添加组件名称,如下所示

.o-componentName {
  &__elementName {
    .-componentName--modifierName & {
      /* modifier styles go here */
    }
  }
}

名称前面的破折号仍然表示它是一个修饰符类。组件名称可防止与不应受到影响的其他组件发生命名空间冲突。双破折号主要是向经典的 BEM 修饰符语法致敬,以双重强调它是一个修饰符类。

以下是手风琴和选项卡示例,但这次应用了命名空间修复

查看 CodePen 上 Daniel Tonon(@daniel-tonon)的笔 Accordion in tabs 2 – fixed

但是,我建议不要默认使用此技术,主要是为了保持 HTML 的简洁性,并避免在多个组件需要共享相同的修饰符时造成混淆。

在大多数情况下,修饰符类用于表示状态的变化,如上例中的手风琴。当元素状态发生变化时,所有子元素(无论属于哪个组件)都应该能够读取该状态变化并轻松响应它。当修饰符类旨在同时影响多个组件时,可能会对该修饰符具体属于哪个组件产生混淆。在这些情况下,对修饰符进行命名空间处理弊大于利。

ABEM 修饰符技术总结

因此,为了最大程度地利用 ABEM 修饰符,请默认使用.-modifierName &&.-modifierName语法(取决于哪个元素具有该类)。

.o-componentName {
  &.-modifierName {
    /* componentName modifier styles go here */
  }

&__elementName {
    .-modifierName & {
      /* elementName modifier styles go here */
    }
  }
}

如果将组件嵌套到自身内部导致问题,请使用直接目标定位。

.o-componentName {
  &__elementName {
    .-nestedModifierName > * > & {
      /* modifier styles go here */
    }
  }
}

如果遇到共享修饰符名称冲突,请在修饰符中使用组件名称。仅当您无法想到其他仍然有意义的修饰符名称时,才执行此操作。

.o-componentName {
  &__elementName {
    .-componentName--sharedModifierName & {
      /* modifier styles go here */
    }
  }
}

上下文相关的样式

严格遵守 BEM 每选择器一个类的 методология 的另一个问题是它不允许您编写上下文相关的样式。

上下文相关的样式基本上是“如果此元素位于此父元素内部,则将其应用于此元素”。

对于上下文相关的样式,存在一个父组件和一个子组件。父组件应负责为子组件应用布局相关的样式,例如边距和位置(.parent .child { margin: 20px })。默认情况下,子组件永远不应该在其组件外部有任何边距。这允许子组件在更多上下文中使用,因为是父组件负责其自己的布局,而不是其子组件。

就像真正的育儿一样,父母应该负责。在涉及父母的布局时,你不应该让他们的淘气无知的子女来做决定。

为了更深入地了解这个概念,让我们假设我们正在构建一个全新的网站,现在我们正在为该网站构建订阅表单组件。

查看 CodePen 上 Daniel Tonon(@daniel-tonon)的笔 Context sensitive 1 – IE unfriendly

这是我们第一次必须在正在构建的这个很棒的新网站上放置表单。我们想成为所有酷孩子中的一员,所以我们使用CSS 网格来进行布局。不过我们很聪明。我们知道按钮样式将在网站的更多地方使用。为了做好准备,我们像优秀的开发人员一样,将订阅按钮样式分离到其自己的单独组件中。

过了一段时间,我们开始进行跨浏览器测试。我们打开 IE11,却发现这个丑陋的东西正盯着我们。

IE11 确实有点支持 CSS 网格,但它不支持grid-gap或自动放置。在经历了一些发泄式的咒骂并希望人们更新他们的浏览器后,您调整了样式,使其看起来更像这样

查看 CodePen 上 Daniel Tonon(@daniel-tonon)的笔 Context sensitive 2 – what not to do

现在它在 IE 中看起来很完美。世界一切安好。还能出什么问题呢?

几个小时后,您将此按钮组件放入网站上的另一个组件中。此其他组件也使用 css-grid 来布局其子元素。

您编写了以下代码

查看 CodePen 上 Daniel Tonon(@daniel-tonon)的笔 Context sensitive 3 – the other component

您希望看到一个即使在 IE11 中也看起来像这样的布局

但实际上,由于您之前编写的grid-column: 3;代码,它最终看起来像这样

哎呀!那么我们如何处理之前编写的grid-column: 3;CSS 呢?我们需要将其限制在父组件中,但我们应该如何进行呢?

好吧,处理此问题的经典 BEM 方法是像这样向按钮添加一个新的父组件元素类

查看 CodePen 上 Daniel Tonon(@daniel-tonon)的笔 Context sensitive 4 – classic BEM solution

表面上,此解决方案看起来还不错

  • 它保持了较低的特异性
  • 父组件控制其自己的布局
  • 样式不太可能泄漏到我们不希望它泄漏到的其他组件中

一切都很棒,世界一切安好……对吧?

这种方法的缺点主要是由于我们必须向按钮组件添加一个额外的类。由于subscribe-form__submit类在基本button组件中不存在,这意味着我们需要向用作模板引擎的任何内容添加额外的逻辑,以便它接收正确的样式。

我喜欢使用Pug来生成我的页面模板。我将以 Pug 混合为例向您展示我的意思。

首先,这是以混合格式重写的原始 IE 不友好代码

查看 CodePen 上 Daniel Tonon(@daniel-tonon)的笔 Context sensitive 5 – IE unfriendly with mixins

现在让我们向其中添加 IE 11 subscribe-form__submit

查看 CodePen 上 Daniel Tonon(@daniel-tonon)的笔 Context sensitive 6 – IE safe BEM solution with mixins

这并不难,那我在抱怨什么呢?好吧,现在假设我们有时希望将此模块放置在侧边栏中。当它在侧边栏中时,我们希望电子邮件输入和按钮彼此堆叠。请记住,为了严格遵守 BEM,我们不允许在样式中使用比单个类更高的特异性。

查看 CodePen 上 Daniel Tonon(@daniel-tonon)的笔 Context sensitive 7 – IE safe BEM with mixins in sidebar

现在 Pug 代码看起来不太容易了,是吗?有几件事导致了这种混乱。

  1. 容器查询将使这个问题变得容易得多,但它们尚未在任何浏览器中原生存在。
  2. 围绕 BEM 修饰符语法的問題正在显现。

现在让我们尝试再次执行此操作,但这次使用上下文相关的样式

查看 CodePen 上 Daniel Tonon(@daniel-tonon)的笔 Context sensitive 8 – IE safe Context Sensitive with mixins in sidebar

看看 Pug 标记变得多么简单。在 Pug 标记中,无需担心“如果这样,则那样”的逻辑。所有这些父级逻辑都传递给 CSS,而 CSS 更擅长理解哪些元素是其他元素的父级。

您可能已经注意到,我在最后一个示例中使用了三个类深的选取器。它用于将按钮的宽度设置为 100%。是的,如果您可以证明它,则可以使用三个类选择器。

我不想在每次使用按钮时都应用 100% 的宽度

  • 在任何地方使用
  • 放置在订阅表单内
  • 放置在侧边栏内

希望在按钮同时位于订阅表单侧边栏内时应用 100% 的宽度。处理此问题的最佳方法是使用三个类选择器。

好的,实际上,我更有可能在subscribe-form元素上使用 ABEM 样式的-verticalStack修饰符类来应用垂直堆叠样式,或者甚至通过使用EQCSS的元素查询来实现。这意味着我可以将垂直堆叠样式应用于比仅在侧边栏中的更多情况下。不过,为了举例说明,我已将其作为上下文相关的样式。

现在我们了解了上下文相关的样式,让我们回到我之前的那个原始示例,并使用一些上下文相关的样式来应用那个麻烦的grid-column: 3规则

查看 CodePen 上 Daniel Tonon(@daniel-tonon)的笔 Context sensitive 9 – context sensitive method with mixins

上下文相关的样式可以简化 HTML 和模板逻辑,同时仍然保留子组件的可重用性。但是,BEM 的每选择器一个类的理念不允许这样做。

由于上下文相关的样式主要与布局有关,因此根据具体情况,您通常应在处理以下 CSS 属性时使用它们

  • 应用于子元素的任何与 CSS 网格相关的属性(grid-columngrid-row等)。
  • 应用于子元素的任何与 flexbox 相关的属性(flex-growflex-shrinkalign-self等)。
  • 大于 0 的margin

  • 除了relative之外的position值(以及topleftbottomright属性)
  • 如果transform用于定位,例如translateY

您可能还想将这些属性放入上下文相关的样式中,但它们在上下文相关的样式中并不经常需要。

  • 宽度(width)
  • 高度(height)
  • 内边距(padding)
  • 边框(border)

但为了绝对清楚起见,上下文相关的样式**不是**为了嵌套而嵌套。您需要将其视为在 JavaScript 中编写if语句。

所以对于这样的 CSS 规则

.parent .element {
  /* context sensitive styles */
}

您应该将其视为编写以下逻辑

if (.element in .parent) {
  .element { /* context sensitive styles */ }
}

还要理解,编写像这样三层嵌套的规则

.grandparent .parent .element {
  /* context sensitive styles */
}

应该被视为编写这样的逻辑

if (
    (.element in .parent) &&
    (.element in .grandparent) &&
    (.parent in .grandparent)
  ) {
  .element { /* context sensitive styles */ }
}

因此,如果您确实认为需要这种级别的特异性,请务必编写一个三层嵌套的 CSS 选择器。但是,请了解您编写的 CSS 的底层逻辑。仅使用对您尝试实现的特定样式有意义的特异性级别。

再说一遍,为了超级清楚,**不要为了嵌套而嵌套!**

总结

我完全赞同 BEM 命名约定背后的方法论。它允许将 CSS 分解成小的易于管理的组件,而不是将 CSS 遗留为难以维护的高特异性杂乱无章的状态。

官方 BEM 语法

  • 不支持原子设计(Atomic Design)
  • 无法轻松扩展
  • 需要更长的时间才能让您的思维处理类名的分组
  • 在管理大型组件的状态方面非常糟糕
  • 试图鼓励您使用单类选择器,而双类选择器更容易维护
  • 试图对*所有内容*进行命名空间,即使命名空间带来的问题多于解决的问题。
  • 如果正确执行,会使 HTML 变得极其臃肿

我非官方的 ABEM 方法

  • 使使用原子设计更容易
  • 释放连字符作为可以用于分组的额外方法
  • 允许您的思维更快地处理类名的分组
  • 擅长处理任何尺寸组件的状态,无论它有多少子组件
  • 鼓励*受控的*特异性,而不仅仅是完全*低*特异性,以减少团队混淆并提高网站的可维护性
  • 在不需要时避免命名空间
  • 保持 HTML 非常简洁,模块上应用的额外类最少,同时仍然保留 BEM 的所有优点

免责声明

我并没有发明-modifier(修饰符名称前的单个连字符)这个想法。我在 2016 年阅读文章时发现了它。我不记得最初是谁构思了这个想法。如果有人知道这篇文章,我很乐意给他们署名。

更新:2018 年 1 月 21 日(评论回复)

没有人能够链接到我了解-modifier语法的文章。我所能说的是,我从阅读一篇关于BEVM(block__element–variation -modifier)的文章中了解到的。

以下是一些在我之前提出-modifier语法的人

如果您喜欢这种方法,BEVM 仍然可以与 ABEM 一起使用(使其成为 ABEVM)。不过,在使用-modifier语法一段时间后,我最终完全停止了使用&--modifier语法。当单个连字符在我的 CSS 中更容易使用并且使我的 HTML 更简洁时,我无法真正看到保留双连字符的任何好处。

有些人提到BEMIT与之非常相似。他们是正确的,它确实与 BEMIT 有一些相似之处,但也有一些区别。

您可以将 ABEM 和 BEMIT 在一定程度上合并在一起。有人提到他们更喜欢 BEMIT 中基于状态的类的明确“is”(例如:.is-active)。这完全没问题,如果您想将“is”添加到 ABEM 中,我建议您像这样编写修饰符.-is-modifierName。明白驼峰命名法允许自定义分组的含义了吗?

实用程序也可以很容易地从 BEMIT 中迁移过来,它仍然会被写成.u-utilityName。“实用程序”文件夹/文件可能应该放置在与原子、分子和有机体文件夹相同的目录中。我认为这可能会更容易找到。BEMIT 中的“对象”和“组件”命名空间将不会迁移。它们将被 ABEM 中的原子设计命名空间替换。

评论中一个有趣的讨论是关于在 Sass 中使用@extend功能。例如,使用<button class='subscribe-form__submit'></button>.subscribe-form__submit { @extend .button; grid-column: 3; }。我认为上下文相关的样式是更好的选择。我非常不同意这种@extend的实现,除非 CMS 强制你走这条路。您可以在此处查看完整的评论和我的回复:https://css-tricks.cn/abem-useful-adaptation-bem/#comment-1613824

许多人遇到的一个问题是,我没有深入探讨原子设计是什么以及如何使用它。这超出了本文的范围。如果我试图深入探讨如何使用原子设计原则对组件进行分类,那么这篇文章的长度将很容易翻倍(而且这篇文章本身已经很长了)。我提供了足够的摘要来介绍原子设计的概念,并链接了一个更深入探讨该主题的资源。这大概是我想对解释原子设计给予的关注。

由于原子设计的分类是一个如此令人困惑的话题,我计划撰写一篇完全关于如何对原子设计组件进行分类的文章。我将尝试创建一套清晰的指南供大家遵循,以帮助确定特定组件属于哪个原子设计类别。不过,不要指望它很快就会出现。它发布还需要一段时间。

更新:2019 年 4 月 2 日(原子分类工具)

因此,撰写原子设计文章的计划从未实现。我开始计划一个,但逐渐意识到,写一篇文章试图解释在对模块进行分类时需要考虑的所有细枝末节是错误的做法。一个最终会给你推荐的工具会更有用。

因此,我开始工作,我很高兴地宣布我的全新原子分类工具发布!

这是一个简单的测验,当您回答问题时,它会为您提供每个类别的积分。问题和积分可能不是 100% 完美,但我认为它在大多数情况下都能很好地对组件进行分类。试一试,它比阅读另外几千字的文字要有趣得多。😊