本文介绍了 BEM、OOCSS、SMACSS 这三种 CSS 规范。BEM 是广泛使用的命名规范,将界面拆分为块、元素和修饰符。OOCSS 以面向对象方式开发,将 CSS 分为结构和皮肤。SMACSS 则要求将 CSS 拆分为基础、布局、模块、状态、主题五部分。文章还对比了它们与现代化原子化 CSS 的优缺点,指出前三者适合长期维护的大型项目,原子化 CSS 适用于小型项目。

关联问题: BEM有哪些实际案例 OOCSS如何提高复用性 SMACSS怎样定义主题

前言

规范系列文章:

很好奇为Vue、React框架实现的各个UI开源库,例如element-plusant-design, 他们有没有基于什么规范编写CSS,采用的哪一种CSS规范?如果查看这些框架生成的CSS,不难发现BEMOOCSSSMACSS的痕迹。本文将为你介绍什么是BEMOOCSSSMACSS,如何将它们运用到CSS编写中。

element-plus CSS: 采用BEM、SMACSS规则

ant-design CSS片段:类似于BEM(不完全是)

为什么要遵守CSS规范?

使用BEM、OOCSS、SMACSS等CSS规范可以提高代码的可读性、维护性、扩展性以及开发体验。通过采用一致且定义明确的命名规范,开发者可以创建出易于理解、维护和协作的CSS代码,这对于保持代码库的整洁和清晰至关重要。

BEM(Block Element Modifier)

BEM是一个使用非常广泛的CSS命名规范,通过BEM可创建易维护、高复用、自解释的CSS代码。BEM是Block Element Modifier的缩写,它将用户界面拆分为独立的块(Block),每一个块有自己的元素(Element)和修饰器(Modifier)

在介绍BEM之前,可通过下面的代码片段对BEM如何拆分界面有直观的了解。

 1<nav class="nav"> 
 2    <ul class="nav__list"> 
 3        <li class="nav__item nav__item--active"> 
 4            <a href="/about" class="nav__link">About</a> 
 5        </li> 
 6        <li class="nav__item"> 
 7            <a href="/pricing" class="nav__link">Pricing</a> 
 8        </li> 
 9        <li class="nav__item"> 
10            <a href="/contact" class="nav__link">Contact</a> 
11        </li> 
12    </ul> 
13</nav>

Block

Block是构成用户界面的组成部分,每一个Block代表一个独立的组件,可以重复使用。Block使用小写字符命名,如果有多个单词,则用连接符-连接。Block下包含有自己的元素(Element)和修饰符(Modifier)。

常用的Block命名有headercontainer、 menu、 checkbox、 inputelement-plus每个组件可理解为一个Block,例如el-cardel-dialog等。and-designelement-plus的区别就是前缀,例如代表card的ant-card

Element

Element在语义上代表Block的一部分,必须与Block相关联,不能独立存在。Element使用双下线__后跟元素名称来命名。

  • 常规的命名如header_logomenu_item
  • 示例中的nav__listnav__itemnav__link,配合Block nav一起使用
  • element-plus中的el-card__headel-card__bodyel-card_foot等,配合Block el-card一起使用

Modifier

修饰符(Modifier)是块或元素的变体。用于改变外观或行为。它们代表块或元素的不同状态,但不会影响现有块或元素的布局。可以使用双破折号 (--) 后跟修饰符名称来命名(例如,menu__item—selected)。

常用的修饰符命名如disabled、 highlightedcheckedfixedsize bigcolor yellow等。

在上面的示例中,nav__item--active 类是修饰符,使用样式高亮当前使用的导航是哪一个。

element-plus中Tag标签的Block名称为el-tag,其下包含el-tag--primaryel-tag--successel-tag--infoel-tag--warning等表示各种状态的修饰符。

对于button组件,通常也会用修饰符标识出各种状态。

 1<button class="button">
 2	Normal button
 3</button>
 4<button class="button button--state-success">
 5	Success button
 6</button>
 7<button class="button button--state-danger">
 8	Danger button
 9</button>

为什么使用BEM

  • 模块化:Block样式不依赖于页面上的其他元素,因此您永远不会遇到级联问题。您还可以将块从已完成的项目转移到新项目。

  • 重复性:以不同的方式组成独立的块,不同页面都可以重用它们,减少您需要维护的CSS代码量。有了一套语义化、明确的样式命名规则,您就可以构建单独模块,使您的CSS完全独立于页面。

  • 结构化:BEM可以基于SMACSS为您的CSS代码提供了一套稳定的结构,并且简单易于理解。

OOCSS(Object-Oriented CSS)

OOCSS是一个以面向对象方式开发模块化、可重用、关注点分离的CSS代码。它把同类的作为对象,按高类聚低耦合方式封StructureSkin,例如button相关的css仅有一份,定义Class el-button(structure)、el-button—primary(skin),不同页面复用这一份css,从而提升了css的可复用性。

Structure、Skin定义

Structure代表了一个页面的骨架,它不关注元素的细节部分(例如color、border或其他装饰骨架的属性),在页面后续的开发过程中Structure也不会有太大的变动,长期保持一个稳定状态。

而Skin作为页面的装饰部分,让你的页面变得更加美观。

Structure:

  • height
  • width
  • margins
  • padding
  • overflow

Skin:

  • colors
  • fonts
  • shadows
  • gradients

假如不同页面同时定义了button的样式.button.button-2:

 1.button { 
 2  width: 150px;
 3  height: 50px; 
 4  background: #FFF; 
 5  border-radius: 5px; 
 6} 
 7
 8.button-2 { 
 9  width: 150px; 
10  height: 50px; 
11  background: #000; 
12  border-radius: 5px; 
13}

两个class包含有重复的样式定义,如果产品要求调整按钮的大小、颜色,一个个页面单独去改,这样的做法就非常难维护。基于OOCSS的概念,我们可以将其拆分为Structure、Skin两部分。改造后的结果为:

 1.btn { 
 2  width: 150px; 
 3  height: 50px; 
 4  border-radius: 5px; 
 5}
 6
 7.btn-light { 
 8  background: #FFF; 
 9} 
10
11.btn-dark{ 
12  background: #000; 
13} 

.btn作为Structure部分,而.btn-light.btn-dark作为Skin部分。在html中使用开起来也变得很丝滑。

 1<a class="btn btn-light" href="#">Home</a> 
 2<a class="btn btn-dark" href="#">Blog</a>

Structure再抽象:Container、Content

Structure和Skin是比较容易区分的两个概念,但就Structure自身,包含了width、height、spacing、border、padidngs、margins等属性,因此它的范围就变得有些复杂。为了将Structure进一步明确,可将其再细化为两个类别:Container、Content。

Container

Container可理解为语义化的虚拟容器,对用户不可见,其他可见元素将包含在这些容器中。例如<div><span><article><navigation><sidebar>

content

content表示实际的内容呈现,例如<img><p><input>button。这些元素可能需要额外的样式,例如定位和排版。 content经常面临的问题是它与skin相混淆。这是因为像排版这样的东西可以既是structure又是skin,这取决于它的使用方式。 OOCSS 的一般规则是,如果它是重复属性,那么它就是strcuture。如果它是变化性的,那么它就是skin

SMACSS(Scalable Modular Architecture for CSS)

不同于OOCSS,SMACSS代表了易扩展、模块化的CSS架构。SMACSS要求将CSS拆分为明确的五个部分:baselayoutmodulestatetheme。通过明确目录拆分,在编写CSS时,哪些CSS该放到哪个目录下是一目了然的事,使用这种方式来管理大型CSS代码库就显得得心应手。

Base

定义全局性初始化默认样式,例如常用的reset.css文件,统一重置paddingmarginorder以及默认的排版样式。

 1
 2
 3
 4*,
 5*::before,
 6*::after {
 7  box-sizing: border-box;
 8}
 9
10
11body,
12h1,
13h2,
14h3,
15h4,
16p,
17figure,
18blockquote,
19dl,
20dd {
21  margin: 0;
22}
23
24
25ul[role='list'],
26ol[role='list'] {
27  list-style: none;
28}

Layout

Layout定义结构化、容器化元素的样式,例如headerfooterarticlessidebarsasides等。为了标识出是layout模块样式,一种实践方式是为这一类的样式添加前缀l-

 1.l-full-width {
 2  width: 100%;
 3}
 4
 5.l-half-width {
 6 width: 50%;
 7}

Module

如果一类样式在界面上多次重复出现,那这一类样式就可归纳到一个模块,例如模块menuswidgetsforms等等。模块可认为是按特定规则将一类可见要素的样式分到同一组中,并且在不同的地方可重复使用。

例如开发Dialog组件,通常使用.el-dialog表示Dialog对应的CSS模块。模块下还包含其他组成Dialog的子要素:

 1/** Module: el-dialog **/
 2.el-dialog {}
 3
 4/** 子模块:header **/
 5.el-dialog__header {}
 6
 7/** 子模块:header **/
 8.el-dialog_title {}
 9
10/** 子模块:headerbtn **/
11.el-dialog__headerbtn {}
12
13/** 子模块:close **/
14.el-dialog__close {}
15
16/** 子模块:body **/
17.el-dialog__body {}
18
19/** 子模块:footer **/
20.el-dialog__footer {}
21

element-plus中每一个组件都可以理解为一个CSS模块, 例如el-buttonel-card等。

State

状态用于描述我们的模块在不同情况下的外观(skin),类似于BEM中的Modifier部分。 SMACSS 的State部分用于定义界面交互性的CSS。 State不必附加到特定对象,例如不必附加到button、card等元素,可独立存在并复用。

element-plus中的checkbox元素,其CSS模块为.el-checkbox,我们可以为每一个checkbox设置不同的状态,例如选中、可用等。根据SMACSS的State规则,可以为这些状态定义.is-disabled.is-checked.is-bordered。这些状态类CSS除了在checkbox中使用,也可以在radiobutton等组件中复用。

 1.is-disabled {
 2    cursor: not-allowed
 3}
 4
 5.is-bordered {
 6    padding: 0 15px 0 9px;
 7    border-radius: var(--el-border-radius-base);
 8    border: var(--el-border);
 9    box-sizing: border-box
10}
11
12is-checked {
13    border-color: var(--el-color-primary)
14}

Theme

主题(Theme)会统筹一体化考虑你的页面风格,在平台级别定义统一的字体颜色、边框、阴影等效果。通过主题化方式,能结合用户不同的喜好定制化多类呈现风格。

当我们在设计平台时就应该考虑CSS支持主题化,这要求研发在编写CSS时应更加离子化的考虑CSS样式拆分。

element-plus在设计CSS时,将skin相关的属性通过scss等CSS框架配置化,例如colorbackgroundborderfont-size等。后期如果想为用户提供其他主题,可直接替换如下代码各个skin变量的值即可。

 1
 2
 3$colors: () !default;
 4$colors: map.deep-merge(
 5  (
 6    'white': #ffffff,
 7    'black': #000000,
 8    'primary': (
 9      'base': #409eff,
10    ),
11    'success': (
12      'base': #67c23a,
13    ),
14    'warning': (
15      'base': #e6a23c,
16    ),
17    'danger': (
18      'base': #f56c6c,
19    ),
20    'error': (
21      'base': #f56c6c,
22    ),
23    'info': (
24      'base': #909399,
25    ),
26  ),
27  $colors
28);

BEM、OOCSS、SMACSS不适用于现代化框架?

现代化CSS框架: CSS原子化

原子化 CSS 是一种 CSS 的架构方式,它倾向于小巧且用途单一的 class,并且会以视觉效果进行命名。

目前提供原子化的框架有Tailwind CSSWindi CSSUnoCSSUnoCSS是作者Anthony Fu基于Windi CSS演变出来的框架, 也是目前主流的原子化CSS框架。CSS原子化和element-plus等UI库CSS实现方式有什么区别?

上图是UnoCSS官网提供的Demo,其实现代码如下所示, 代码采用UnoCSS提供的CSS原子化方式编写。 UnoCSS将不同维度的style属性值拆分成离子化的class,如h-fulltext-centerjustify-centertext-2xl等,这些class看起来七离八碎。

 1<div h-full text-center flex select-none all:transition-400>
 2  <div ma>
 3    <div text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s>
 4      UnoCSS
 5    </div>
 6    <div op30 text-lg fw300 m1>
 7      The instant on-demand Atomic CSS engine.
 8    </div>
 9    <div m2 flex justify-center text-2xl op30 hover="op80">
10      <a
11        i-carbon-logo-github
12        text-inherit
13        href="https:
14        target="_blank"
15      ></a>
16    </div>
17  </div>
18</div>
19<div absolute bottom-5 right-0 left-0 text-center op30 fw300>
20  on-demand · instant · fully customizable
21</div>

如果以BEMOOCSSSMACSS方式实现同样的效果,其伪HTML代码如下。

 1 
 2  <div class="hero">
 3    <div class="hero__container">
 4      
 5      <div class="hero__title">
 6        UnoCSS
 7      </div>
 8      <div class="hero__subtitle">
 9        The instant on-demand Atomic CSS engine.
10      </div>
11      <div class="hero__social">
12        <a
13          class="hero__github-link"
14          href="https://github.com/unocss/unocss"
15          target="_blank"
16        >
17          <span class="icon icon--github"></span>
18        </a>
19      </div>
20    </div>
21  </div>
22  
23  <div class="slogan">
24    on-demand · instant · fully customizable
25  </div>

对比两种实现方式,CSS原子化方式的缺点:

  • CSS冗长:为了实现一个元素的交互效果,样式会附加大量的原子化CSS Class,如一长串的text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s;
  • 理解成本比较高:维护这些CSS时,需要花时间理解每个名字背后的样式,除非借助IDE插件查看每个class包含的样式;
  • 不易维护:对于一个大型项目,如果要扩展或修改同类型元素的样式时,面对一大堆原子化CSS就显得束手无策;

以上这些体现在CSS原子化框架上的缺点,在BEMOOCSSSMACSS面前都不是问题,结构化、语义化的CSS天生就显得简洁,容易理解,方便维护。

总结

BEM提供命名规则,OOCSS、SMACSS提供方法论,这三者之间不是互斥关系,一个平台完全可以结合三者各自的优势来设计。SMACSS从宏观角度考虑CSS按哪种模块拆分合理,而OOCSS确定一个模块中的CSS按什么结构来组织,在具体落地到CSS编写时通过BEM来规范命名。

BEMOOCSSSMACSS对比现代化的原子化CSS原子化CSS可理解为是结构化、模块化的CSS的更细粒度的裂变,例如将10个class裂变为30个class,提升更细粒度的复用。这种拆分为明确的、有规律的css的方式,非常适用于小型、低代码或前端AI生成项目。相反,BEMOOCSSSMACSS更适合于需长期维护的大型项目。

参考

  1. Understanding CSS naming conventions: BEM, OOCSS, SUIT CSS, and SMACSS
  2. What is BEM
  3. Scaling Down The BEM Methodology For Small Projects
  4. CSS 架构之OOCSS
  5. CSS 模块化方案探讨(BEM、OOCSS、CSS Modules、CSS-in-JS …)
  6. What is OOCSS and How OCSS Works
  7. What Is SMACSS and How Does It Work?

我是前端下饭菜,原创不易,各位看官动动手,帮忙关注、点赞、收藏、评论!

个人笔记记录 2021 ~ 2025