结合 SEM 和 BIO 的力量来改进 CSS

Avatar of Ryan Yu
Ryan Yu

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

有人可能会说 CSS 很容易,但这种“容易”会导致代码混乱。在使用 Sass 或 Less 等预处理器时尤其如此,如果您不小心,您的 CSS 会变得更难处理,而不是更容易。Sass?更难?此 Gist 展示了 Sass 嵌套地狱的一个很好的例子。

如果您的 Sass 代码看起来像那样,您绝对可以通过 **SEM & BIO** 来改进您的代码,这是一种我将在本文中向您介绍的 CSS 技术!

在本文中,我将使用下面的代码示例来解释 SEM 和 BIO 的工作原理以及它们如何帮助增强您的 CSS 策略。

查看 thebabydino 在 Pen 上的代码 (@thebabydino),位于 CodePen 上。

通常,SEM 关注的是高级 CSS 理念,而 BIO 是一种实际的技术,可以帮助您编写更好的 CSS 来实现 SEM。SEM 和 BIO 的主要目的是更好地处理 CSS 特定性,这是您应该了解 CSS 的最重要概念之一。

鸣谢:Nana Jeon

首先,让我们谈谈 SEM。

SEM

SEM 代表

  • 可扩展性
  • 可扩展性
  • 可维护性

努力实现这三个因素肯定会改善您的 CSS 代码,并帮助您构建更可靠的组件。

让我们更详细地讨论每个因素。

可扩展性

可扩展(或可重用)组件意味着,无论您想在何处使用外观相同的组件,都无需进行任何代码更改。

鸣谢:Nana Jeon

从上面的 CodePen 示例中,标题中的“搜索”按钮与侧边栏中的“链接”按钮看起来完全相同。当我们比较 HTML 标记时,

  • “搜索”按钮是 <button> 元素
  • 但“链接”按钮是 <a role="button" ...> 元素

…即使标记不同,样式也通过使用相同的类保持一致:.c-btn.c-btn--yellow

按钮样式是可扩展的,它允许您在需要的地方添加外观相同的组件,因为它的父级或同级不会污染它。这可以免除您不知道为什么完全无关的组件会损坏的巨大麻烦,即使这些更改是在完全不同位置的另一个组件上进行的。

来源:Cartoon Network

可扩展性

可扩展组件可以轻松地提供额外的功能/功能,而不会破坏自身或需要从头开始编写。

让我们再次看一下 CodePen 示例。

查看 iamryanyu 在 Pen 上的代码 (@iamryanyu),位于 CodePen 上。

标题和主部分中的按钮看起来非常相似,除了 3D 效果。在这种情况下,我们可以通过只添加 3D 效果来扩展普通按钮样式,而不是创建两个具有完全不同代码库的不同按钮集。

页脚中的按钮也是如此。即使按钮具有不同的颜色和大小,我们也可以通过添加或删除新的或不同的功能来轻松地扩展它。

鸣谢:Nana Jeon

可维护性

对于大多数前端开发人员来说,最大的挑战之一可能是理解其他人或过去自我的 CSS 代码。我们有时花费更多时间试图理解现有的代码,而不是添加精心编写的新的代码。

问题通常来自

  • 没有任何注释
  • 过度设计
  • 没有单一的事实来源
  • 没有考虑编码标准/最佳实践
  • 或以上所有
鸣谢:Nana Jeon

使用 SEM 和 BIO,我们绝对可以改进代码,并让其他人(包括我们自己!)免受混乱、难以维护的代码的困扰。

BIO

有很多很棒的技术可以改进我们编写 CSS 的方式,根据我的经验,我发现以下三种构成 BIO 首字母缩略词的技术可以很好地协同工作。

  • BEM
  • ITCSS
  • OOCSS
鸣谢:Nana Jeon

许多开发人员/工程师已经了解这些著名的技术,但我想逐一介绍它们,并讨论我使用这些技术的方式。

BEM

BEM 是一种非常流行的方法,它一直在帮助我们显著改进我们对 CSS 和 Sass/Less 的思考方式。

BEM 代表

  • 元素
  • 修饰符
鸣谢:Nana Jeon

正如上面的糟糕示例所示,我们倾向于过度使用 Sass/Less 的功能,从而陷入嵌套地狱。但使用 BEM,我们通过在 CSS 特定性中保持一到两层嵌套的样式,开始拥有非常低的 CSS 特定性。

如果您经历过任何与更高 CSS 特定性作斗争的战斗,您就会知道赢得胜利有多么痛苦。

回到我们的示例,HTML 标记看起来像这样

<div class="o-grid">
  <div class="o-grid__item o-grid__header">
    ...
  </div>
  <div class="o-grid__item o-grid__main">
    ...
  </div>
  <div class="o-grid__item o-grid__sidebar">
    ...
  </div>
  <div class="o-grid__item o-grid__footer">
    ...
  </div>
</div>

该示例包含

  • 一个块:.o-grid
  • 元素:.o-grid__item.o-grid__header.o-grid__main.o-grid__sidebar.o-grid__footer

因为 BEM 提供了一种命名约定,强调独特的类,所以我们不必进行像

.o-grid {
  .o-grid__item {
    ...
  }
}

相反,我们可以使用较少的层级定义它的样式

.o-grid__item {
  ...
}

这是 BEM 最大的优点;降低 CSS 特定性,从而提高整个 CSS 编码效率和体验。

即使使用 BEM,我仍然偶尔会看到一个问题,即命名不当。如果您不够注意,您最终可能会得到一个非常长的类名,例如

/* Yikes! */
.o-grid__item-search-button-text-svg-icon {
  ...
}

创建类名时,遵循 BEM 的核心概念:您的组件是一个块,块内部的所有元素都分别附加到该块。

再次从我们的示例中,我命名了 .o-grid__form 而不是 .o-grid__item-form,因为表单本身是一个单独的组件,不需要绑定到 o-grid__item 并作为其子级。

此外,为了更有效地控制样式,我在 .o-grid__item 旁边添加了另一个类名 o-grid__header 来扩展样式。此外,按钮包含使用 OOCSS 方法的 BEM 风格类,我们将在下一节中讨论。

OOCSS

正如我们已经讨论的那样,有很多很棒的 CSS 方法和策略可以帮助我们改进编写 CSS 的方式。但是,我看到很多人强迫自己从一堆方法中选择一种方法。

根据我的经验,结合多种方法实际上可以通过结合多个世界的精华来增强它们的好处。例如,我个人发现 BEM 和 OOCSS 可以很好地协同工作。

OOCSS 代表 **面向对象的 CSS**,您可以将其想象成像乐高积木一样工作。

来源:Flickr

OOCSS 分别创建每个单独的部分,然后将它们组合在一起以构建组件。

从我们的示例中,我使用 OOCSS 命名约定创建了按钮

  • .c-btn
  • .c-btn--yellow
  • .c-btn--blue
  • .c-btn--3d
  • .c-btn--large

为了在我们的示例标题中呈现黄色的搜索按钮,我们将这些类组合在一起

  • .c-btn
  • .c-btn--yellow

如果我们想要在主部分中使用 3D 按钮,则添加 3D 类 .c-btn--3d,然后就可以了。

对于页脚中的蓝色按钮,我们可以将黄色的修饰符切换为蓝色,以及大的修饰符。如您所见,按钮不依赖于标题块,这使我们能够更灵活地使用和重用组件。而且,通过这样做,我们可以构建按钮而不会影响任何其他组件或模式,同时获得轻松扩展新的表示性功能的益处,例如备用颜色和形状。

以下是使用 OOCSS 创建按钮集合以创建变体的示例

查看 CodePen 上 Ryan Yu (@iamryanyu) 的 现代按钮集合

在 BEM 和 OOCSS 的基础上,借助 ITCSS,我们可以进一步改进我们的 CSS 策略。 让我们接下来看看这种方法。

ITCSS

ITCSS 代表 **倒三角形 CSS**,它通过应用确定特定组件的具体程度的结构来帮助组织 CSS。 Lubos Kmetko 撰写了 关于 ITCSS 的优秀概述,值得一读。

您可以在 此 Gist 中看到我如何通过将样式按分组的特定级别拆分来使用 ITCSS。

根据该示例,您可以看到我如何通过向类添加命名空间来命名 **组件**。 例如,"按钮" 组件以 "c" 为前缀 (.c-button) 来指示组件状态,并防止将其误认为其他项目。 这样,所有参与项目的人员都知道该特定类的功能以及更改其属性可能会对其他区域造成的影响。

以下是一个说明所有 ITCSS 级别 的可视化图表

让我们逐节看一下。

设置

设置通常是一组不会生成 CSS 但会应用于类的变量。 一些示例包括

  • 基础
  • 颜色
  • 排版
  • 动画
工具

工具也不会产生任何 CSS,它们通常是预处理器函数,有助于编写或扩展类上的属性

  • 函数
  • 占位符
  • 混合
  • 媒体查询
供应商

供应商是项目中使用的第三方样式。 想象一下像 reset.css、normalize.css 甚至 Foundation 和 Bootstrap 之类的东西。

这些样式在结构中处于较高的位置的原因是,以便我们可以在需要时覆盖它们。 如您所知,如果两次调用相同的类,级联将呈现第二个实例的属性,假设属性完全相同。

.btn--large {
  padding: 3em;
}

/* This one wins out */
.btn--large {
  padding: 5em;
}

作为旁注,在 Sass 中,您可以使用 ~ 指向 node_modules 文件夹,以便您可以从源代码导入样式资产,而不必将其移动到您自己的目录中。

@import '~modern-normalize/modern-normalize';
对象

对象 (命名空间:o-) 用于设计模式,例如布局,其中项目被排列而不是装饰。 对象类在所有页面中使用,因此,如果您对对象类进行任何更改,您应该非常小心,因为任何更改都会影响整个网站的每个页面。

我使用的最常见的对象类是

  • .o-page:最外层的容器,通常包含 max-width: 100vwoverflow: hidden
  • .o-main:主区域的外层容器。
  • .o-container:组件的外层容器,通常提供固定宽度。
  • .o-content:如果实际内容区域需要额外的配置。
  • .o-grid:如果需要具有不同列数的网格布局。

您使用其他对象类吗? 如果是,请与我分享。 😃

元素

元素 (命名空间:e-) 是我们不会根据类名设置样式的 HTML 本机元素。 例如,我们应该为 <a> 元素提供默认样式,而不是 .link 类。

// Do this for the default link style
a {
  text-decoration: none;

  &:hover {
    background-color: blue;
    color: white;
  }
}

// Don’t provide the default link style to a class
.link {
  text-decoration: none;

  &:hover {
    background-color: blue;
    color: white;
  }
}

这是因为,尤其是在 WordPress 等 CMS 中,您不希望每次在内容中使用链接时都添加一个类。 因此,我们为 <a> 元素提供默认样式,因此无需任何类,链接将仍然具有美观的样式。

组件

组件 (命名空间:c-) 是构成网站一部分的小功能。 想象一下按钮、手风琴、滑块、模态对话框等。 每个组件本身都完全可行,并且不依赖于任何其他组件。 在命名组件时应考虑这一事实。

例如,以上示例中主部分中的按钮不应称为 .c-main-button,因为主部分将其限定在 main 部分内,并限制了它在其他地方(如侧边栏)的使用。 像 .c-btn 这样的东西要好得多,因为按钮不再与页面的任何其他特定部分绑定。

如果您需要任何额外功能,您始终可以使用 BEM 修饰符(结合力量!)或使用 **范围** 来扩展属性,这将在稍后介绍。

模式

许多开发人员/工程师将 *组件* 和 *模式* 作为同义词使用,如果您对此感到更舒适,那完全没问题。 我只是更喜欢将这两个术语分开。

作为一般规则,我认为模式(命名空间:p-)是组件的组合,但它 **不可** 扩展。

例如,我认为手风琴是一个组件。 它本身是可扩展的和可重用的,这意味着它可以在网站的其他部分使用而无需进行任何更改,即使手风琴包含其他组件,例如按钮。

另一方面,例如,页眉将是一个模式,因为它 **不可** 扩展(页眉不能在内容或侧边栏区域使用),并且还包含其他组件,例如按钮、手风琴、菜单、徽标、搜索表单等。

范围

请注意,我 *只* 在绝对必要时使用范围。 范围(命名空间:s-)的目的是为我们提供最高的特定性,以便我们可以覆盖特定目的的任何样式。

请记住,如果您发现自己多次使用 *范围类*,那么您可能正在编写 *过于* 特定的样式,您应该考虑重构您的 CSS 结构。

以下是如何使用范围类 .s-home 的简单示例。

.c-accordion {
  .s-home & {
    // Changing the background color specically on the homepage
    background-color: tomato;
  }
}

作为旁注,上述示例实际上可以通过为手风琴提供一个修饰符(例如,.c-accordion--bg-tomato)来重构,而不是使用范围类。 这将是更可扩展的编写方式,并使组件更模块化。

实用程序

有时您可能只想在特定位置更改特定样式。 在这种情况下,实用程序(命名空间:u-)类可以帮助我们更新它,而无需更改整个 CSS 结构。

例如,手风琴标题的字体大小设置为 32px。

.c-accordion__heading {	 	 
  font-size: rem(32);	 	 
}

但是,如果字体大小只在您网站的新闻部分有所不同,而在其他任何地方都没有改变,那么您可能希望应用实用程序类,而不是使用父类或范围类设置更高的特异性。

<button aria-expanded="false" class="c-accordion__heading u-font-size--24" aria-controls="sect1" id="accordion1id" type="button">...</button>
.u-font-size--24 {	 	 
  font-size: rem(24) !important;	 	 
}

请注意,我们都知道!important很糟糕,但我将!important添加到值中。这是因为当我们使用实用程序类时,我们绝对确定我们希望以我们想要的方式更新特定的样式。此外,实用程序类应该覆盖任何其他样式,因此在这里使用!important实际上非常适合实用程序类。也就是说,实用程序类只应该作为助手发挥作用。它不应该用于构建您的 CSS 结构。

与范围类类似,如果您使用太多实用程序类,您应该与设计师确认设计是否可以在整个网站上保持一致。

额外命名空间

除了我们上面讨论的命名空间之外,我还经常使用另外两个命名空间。

  • is-:这表示块或元素的状态。最常用的类是.is-active,例如导航中的活动链接。
  • js-:这表示特定元素绑定到 JavaScript 事件。例如,js-menu-click表示该元素绑定到点击事件。

代码风格检查

最后,使用.stylelint.eslint制定规则可以显着提高代码质量。

在前端工作流程中,我不把它作为建议,而是作为强制性要求,这样违反规则的行为将不会被批准。

通过这种方式,我们可以确保代码质量保持最佳状态,并为其他开发人员提供更好的代码,包括您未来的自己。

实际应用

在本节中,我想讨论如何使用 SEM 和 BIO。我做了一个简单实用的例子来帮助我们开始。

查看 iamryanyu 在 CodePen 上的 示例 (@iamryanyu)。

该示例的主要实践是构建一个手风琴,它可以作为以下内容使用:

  • 普通手风琴,但在主部分使用不同的颜色主题
  • 侧边栏中的菜单
  • 在页脚中显示社交媒体图标的块

我们正在实现的是一个具有以下特点的组件:

  • 可扩展的:因为它可以添加到页面的任何部分而无需任何代码更改
  • 可扩展的:因为它可以在核心功能不变的情况下提供不同的功能
  • 可维护的:因为它以一种有意义的方式进行了组织

为了实现 SEM,BIO 已被使用,包括:

  • BEM:.c-accordion 作为块,其子元素作为元素,还使用修饰符,例如,.c-accordion--light.c-accordion--dark
  • ITCSS:SASS 文件的排序/排序很好地处理了 CSS 特异性。例如,侧边栏中的手风琴按钮包含class="c-accordion__trigger p-sidebar-menu__button",其中模式 (p-) 覆盖了组件 (c-) 没有任何问题。
  • OOCSS:手风琴由多个类构建,例如,class="c-accordion c-accordion--dark c-accordion--single",它创建了一个深色主题,每次只打开一个面板。

最后的想法

我几乎在我的所有项目中都使用了这种方法,包括大学、政府部门、商业零售商以及许多其他网站。在每种情况下,我都成功地将所有项目交付给客户(在客户审批阶段几乎没有问题,并且按时交付);到目前为止,这种方法对我很有效,我认为它也可能对您有效。

也就是说,技术总是在变化(尤其是在前端),我很乐意听到和讨论您对您有效的任何想法/方法/策略。请在评论中告诉我!