优秀框架都在使用的CSS规范: BEM、OOCSS、SMACSS

2024-11-20
421515 分钟
...

本文介绍了 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如何拆分界面有直观的了解。

<nav class="nav"> 
    <ul class="nav__list"> 
        <li class="nav__item nav__item--active"> 
            <a href="/about" class="nav__link">About</a> 
        </li> 
        <li class="nav__item"> 
            <a href="/pricing" class="nav__link">Pricing</a> 
        </li> 
        <li class="nav__item"> 
            <a href="/contact" class="nav__link">Contact</a> 
        </li> 
    </ul> 
</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组件,通常也会用修饰符标识出各种状态。

<button class="button">
	Normal button
</button>
<button class="button button--state-success">
	Success button
</button>
<button class="button button--state-danger">
	Danger button
</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:

.button { 
  width: 150px;
  height: 50px; 
  background: #FFF; 
  border-radius: 5px; 
} 

.button-2 { 
  width: 150px; 
  height: 50px; 
  background: #000; 
  border-radius: 5px; 
}

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

.btn { 
  width: 150px; 
  height: 50px; 
  border-radius: 5px; 
}

.btn-light { 
  background: #FFF; 
} 

.btn-dark{ 
  background: #000; 
} 

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

<a class="btn btn-light" href="#">Home</a> 
<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以及默认的排版样式。




*,
*::before,
*::after {
  box-sizing: border-box;
}


body,
h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd {
  margin: 0;
}


ul[role='list'],
ol[role='list'] {
  list-style: none;
}

Layout

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

.l-full-width {
  width: 100%;
}

.l-half-width {
 width: 50%;
}

Module

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

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

/** Module: el-dialog **/
.el-dialog {}

/** 子模块:header **/
.el-dialog__header {}

/** 子模块:header **/
.el-dialog_title {}

/** 子模块:headerbtn **/
.el-dialog__headerbtn {}

/** 子模块:close **/
.el-dialog__close {}

/** 子模块:body **/
.el-dialog__body {}

/** 子模块:footer **/
.el-dialog__footer {}

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等组件中复用。

.is-disabled {
    cursor: not-allowed
}

.is-bordered {
    padding: 0 15px 0 9px;
    border-radius: var(--el-border-radius-base);
    border: var(--el-border);
    box-sizing: border-box
}

is-checked {
    border-color: var(--el-color-primary)
}

Theme

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

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

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



$colors: () !default;
$colors: map.deep-merge(
  (
    'white': #ffffff,
    'black': #000000,
    'primary': (
      'base': #409eff,
    ),
    'success': (
      'base': #67c23a,
    ),
    'warning': (
      'base': #e6a23c,
    ),
    'danger': (
      'base': #f56c6c,
    ),
    'error': (
      'base': #f56c6c,
    ),
    'info': (
      'base': #909399,
    ),
  ),
  $colors
);

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看起来七离八碎。

<div h-full text-center flex select-none all:transition-400>
  <div ma>
    <div text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s>
      UnoCSS
    </div>
    <div op30 text-lg fw300 m1>
      The instant on-demand Atomic CSS engine.
    </div>
    <div m2 flex justify-center text-2xl op30 hover="op80">
      <a
        i-carbon-logo-github
        text-inherit
        href="https:
        target="_blank"
      ></a>
    </div>
  </div>
</div>
<div absolute bottom-5 right-0 left-0 text-center op30 fw300>
  on-demand · instant · fully customizable
</div>

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

 
  <div class="hero">
    <div class="hero__container">
      
      <div class="hero__title">
        UnoCSS
      </div>
      <div class="hero__subtitle">
        The instant on-demand Atomic CSS engine.
      </div>
      <div class="hero__social">
        <a
          class="hero__github-link"
          href="https://github.com/unocss/unocss"
          target="_blank"
        >
          <span class="icon icon--github"></span>
        </a>
      </div>
    </div>
  </div>
  
  <div class="slogan">
    on-demand · instant · fully customizable
  </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?

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

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
css2026-06-17
外阴影和内阴影介绍
在前端页面设计中,阴影是非常常见的视觉效果。它可以让元素看起来更有层次感,也可以用来表达悬浮、按压、凹陷、卡片、按钮等状态。 CSS 中最常用的阴影属性是 。它既可以实现外阴影,也可以实现内阴影。 一、什么是外阴影 外阴影指的是元素外部的阴影效果。 它会出现在元素边框之外,让元素看起来像是从页面中“浮起来”了一样。常见的卡片、弹窗、按钮悬浮效果,基本都会用到外阴影。 例如: 这个阴影表示:元素下方有一层柔和的黑色阴影,让卡片看起来更有立...
学习
css2026-05-15
border-image-slice详细介绍
是 CSS 系列属性里最关键、也最容易让人懵的一个属性。 它的作用可以简单理解为: 把一张图片切成九宫格,然后决定哪几块用来绘制元素的边框。 它通常和这些属性一起使用: 或者简写成: 其中 决定“怎么切图”。 一、 是什么? 普通的 只能画纯色边框: 但有时候我们想用图片作为边框,比如: 游戏按钮边框 像素风 UI 复古窗口 聊天气泡 卡牌边框 花纹装饰框 九宫格按钮背景 这时候就可以用 。 例如一张按钮图片长这样: 我们希望按钮变宽时...
学习
css2026-03-05
如何在最小副作用下创建 BFC
在 CSS 布局的江湖中, BFC (Block Formatting Context,块级格式化上下文) 是一个老生常谈却又至关重要的概念。无论是解决 高度塌陷 、 清除浮动 ,还是防止 垂直外边距重叠 ,BFC 都是我们的得力助手。 但是,创建 BFC 的方法有很多: 、 、 、 …… 哪种方法对现有布局的“破坏”最小? ❌ 过去的老方法 在过去,为了触发 BFC,我们不得不使用一些带有副作用的“黑客”技巧: : 元素会脱离文档流,...
学习
css2026-03-03
CSS过渡性能解析:transition: all
CSS过渡性能深度解析:为什么 可能是你页面的隐形杀手 一、引言:一个被忽视的性能陷阱 你是否曾在项目中这样写: 看似简洁优雅,却可能在低端设备上引发卡顿、掉帧。 真相 : 是前端性能优化中高频出现的“隐形陷阱”。本文将彻底揭开它的面纱。 二、核心概念: 到底在控制什么? 指定 哪些CSS属性变化时触发动画过渡 。它是过渡效果的“守门员”: | 值 | 含义 | 生成的CSS | | | | | | | 所有 可过渡属性变化时均触发动画...
学习
css2026-01-26
在CSS中,四种高度视口单位的区别
在CSS中,视口单位是构建响应式网页设计的重要工具,特别是针对移动端开发。除了传统的 ,CSS新引入了 、 和 等更精确的单位,解决传统单位在移动端的局限性。 四种高度视口单位的区别 | 单位 | 全称 | 含义 | 特点 | | | | | | | vh | Viewport Height | 传统视口高度单位,1vh = 视口高度的1% | 在移动端有问题:不考虑浏览器UI变化,经常导致内容被遮挡或出现意外滚动 | | svh |...
学习
css2025-11-04
keyframes 关键帧机制
一、什么是 @keyframes? 用于定义动画在不同时间点(关键帧)的样式变化;配合 属性应用到元素上。 定义动画 :使用 声明关键帧序列; 应用动画 :在选择器中通过 / 相关长属性控制表现。 二、基本语法 等价写法( / ): 三、与 属性配合使用 /相关长属性 | 属性名 | 含义 | 示例值 | | : | : | : | | | 动画名称 | | | | 持续时长 | 、 | | | 速度曲线 | / / / | | | 延...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录