我是前端下饭菜,两娃的爸创业中。公众号“绘个球”(各种号全网同名)实时分享创业动态,提供军事、地理、地产短视频工具。
前言
规范系列文章:
很好奇为Vue、React框架实现的各个UI开源库,例如element-plus、ant-design, 他们有没有基于什么规范编写CSS,采用的哪一种CSS规范?如果查看这些框架生成的CSS,不难发现BEM、OOCSS、SMACSS的痕迹。本文将为你介绍什么是BEM、OOCSS、SMACSS,如何将它们运用到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命名有header、container、 menu、 checkbox、 input。element-plus每个组件可理解为一个Block,例如el-card、el-dialog等。and-design和element-plus的区别就是前缀,例如代表card的ant-card。
Element
Element在语义上代表Block的一部分,必须与Block相关联,不能独立存在。Element使用双下线__后跟元素名称来命名。
- 常规的命名如
header_logo、menu_item等 - 示例中的
nav__list、nav__item、nav__link,配合Blocknav一起使用 element-plus中的el-card__head、el-card__body、el-card_foot等,配合Blockel-card一起使用
Modifier
修饰符(Modifier)是块或元素的变体。用于改变外观或行为。它们代表块或元素的不同状态,但不会影响现有块或元素的布局。可以使用双破折号 (--) 后跟修饰符名称来命名(例如,menu__item--selected)。
常用的修饰符命名如disabled、 highlighted, checked, fixed, size big, color yellow等。
在上面的示例中,nav__item--active 类是修饰符,使用样式高亮当前使用的导航是哪一个。
element-plus中Tag标签的Block名称为el-tag,其下包含el-tag--primary、el-tag--success、el-tag--info、el-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代码。它把同类的作为对象,按高类聚低耦合方式封Structure和Skin,例如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拆分为明确的五个部分:base、layout、module、state、theme。通过明确目录拆分,在编写CSS时,哪些CSS该放到哪个目录下是一目了然的事,使用这种方式来管理大型CSS代码库就显得得心应手。
Base
定义全局性初始化默认样式,例如常用的reset.css文件,统一重置padding、margin、order以及默认的排版样式。
/* base.css */
/* Box sizing rules */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Remove default margin */
body,
h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd {
margin: 0;
}
/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
ul[role='list'],
ol[role='list'] {
list-style: none;
}
Layout
Layout定义结构化、容器化元素的样式,例如header、footer、articles、sidebars、asides等。为了标识出是layout模块样式,一种实践方式是为这一类的样式添加前缀l-。
.l-full-width {
width: 100%;
}
.l-half-width {
width: 50%;
}
Module
如果一类样式在界面上多次重复出现,那这一类样式就可归纳到一个模块,例如模块menus、widgets、forms等等。模块可认为是按特定规则将一类可见要素的样式分到同一组中,并且在不同的地方可重复使用。
例如开发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-button、el-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中使用,也可以在radio、button等组件中复用。
.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框架配置化,例如color、background、border、font-size等。后期如果想为用户提供其他主题,可直接替换如下代码各个skin变量的值即可。
/** element-plus **/
$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 CSS,Windi CSS、UnoCSS, UnoCSS是作者Anthony Fu基于Windi CSS演变出来的框架, 也是目前主流的原子化CSS框架。CSS原子化和element-plus等UI库CSS实现方式有什么区别?

上图是UnoCSS官网提供的Demo,其实现代码如下所示, 代码采用UnoCSS提供的CSS原子化方式编写。 UnoCSS将不同维度的style属性值拆分成离子化的class,如h-full、text-center、justify-center、text-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://github.com/unocss/unocss"
target="_blank"
></a>
</div>
</div>
</div>
<div absolute bottom-5 right-0 left-0 text-center op30 fw300>
on-demand · instant · fully customizable
</div>
如果以BEM、OOCSS、SMACSS方式实现同样的效果,其伪HTML代码如下。
<!-- 使用BEM命名方式: block__element--modifier -->
<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原子化框架上的缺点,在BEM、OOCSS、SMACSS面前都不是问题,结构化、语义化的CSS天生就显得简洁,容易理解,方便维护。
总结
BEM提供命名规则,OOCSS、SMACSS提供方法论,这三者之间不是互斥关系,一个平台完全可以结合三者各自的优势来设计。SMACSS从宏观角度考虑CSS按哪种模块拆分合理,而OOCSS确定一个模块中的CSS按什么结构来组织,在具体落地到CSS编写时通过BEM来规范命名。
BEM、OOCSS、SMACSS对比现代化的原子化CSS,原子化CSS可理解为是结构化、模块化的CSS的更细粒度的裂变,例如将10个class裂变为30个class,提升更细粒度的复用。这种拆分为明确的、有规律的css的方式,非常适用于小型、低代码或前端AI生成项目。相反,BEM、OOCSS、SMACSS更适合于需长期维护的大型项目。
参考
- Understanding CSS naming conventions: BEM, OOCSS, SUIT CSS, and SMACSS
- What is BEM
- Scaling Down The BEM Methodology For Small Projects
- CSS 架构之OOCSS
- CSS 模块化方案探讨(BEM、OOCSS、CSS Modules、CSS-in-JS ...)
- What is OOCSS and How OCSS Works
- What Is SMACSS and How Does It Work?
我是
前端下饭菜,原创不易,各位看官动动手,帮忙关注、点赞、收藏、评论!