我是前端下饭菜,两娃的爸创业中。公众号“绘个球”(各种号全网同名)实时分享创业动态,提供军事、地理、地产短视频工具。
前言
规范系列文章:
很好奇为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?
我是
前端下饭菜
,原创不易,各位看官动动手,帮忙关注、点赞、收藏、评论!