致敬
OO-CSS
作为前辈中的前辈,OO-CSS的概念提出已久(2009),官网地址:http://oocss.org/
(本人倾向于将下面的OOCSS、BEM归类为OO-CSS。)
OOCSS
CSS“对象”
OOP编程,模块以对象为单位。在OOCSS的概念里,CSS“对象”是一个可重复的视图。
CSS“对象”由以下四部分组成:
- HTML,可以是DOM的一个或多个节点;
- CSS声明,关于这些DOM节点样式的CSS声明,所有这些节点都以包装节点的类名开头;
- 组件,如背景图片,sprites等用于展示资源的;
- 与对象关联的javascript行为、侦听器或方法。
如:
<div class="mod">
<div class="inner">
<div class="hd">Block Head</div>
<div class="bd">Block Body</div>
<div class="ft">Block Foot</div>
</div>
</div>
.mod .inner {}
.mod .inner .hd {}
...
在这里,mod是一个CSS“对象”,它包含四个属性节点(不能独立于模块生存,包括两个必需的区域:内部和主体,以及两个可选的区域:头部和脚)。
两大原则
分离结构和皮肤
分离结构和皮肤意味着要将重复的样式特征(如背景和边框等样式)定义为单独的“皮肤”,通过和其他各种CSS“对象”的混合及匹配,使得在没有太多代码的情况下实现大量的视觉变化。
比如说一个渐变按钮,那么 .btn 的 class 是不会包含渐变相关的属性的,而是需要单独抽取出一个渐变的 class,然后让 .btn 去扩展从而得到一个渐变的按钮。
可以看下面这个例子:
<div class="content">吴彦祖</div>
.content {
margin: 20px auto;
width: 150px;
height: 150px;
color: red;
background: blue;
border: 1px solid yellow;
}
没有分离结构与皮肤,OOCSS并不推荐这样做。可改为:
<div class="content skin-blue">吴彦祖</div>
.content {
margin: 20px auto;
width: 150px;
height: 150px;
}
.skin-blue {
color: red;
background: blue;
border: 1px solid yellow;
}
并且,分离结构和外观还意味着使用类来命名对象及其组件,而不仅仅依赖于HTML的语义。例如,媒体对象以class="media"命名,其图片组件以class="img"来命名,其文本以class="bd"来命名。
通过在样式表中引用这些类(比如不是直接为<img>
元素设置样式),这样你的HTML可以很灵活。
区分容器和内容
区分容器和内容意味着将很少使用位置相关的样式,一个CSS“对象”应该不管放到哪里看起来都一样。所以不要用.myObject h2{ ... }
来设置特定的<h2>
样式,而是应该创建并应用一个描述与<h2>
相关的class,如<h2 class="category">
。
这样能够使您保证:
- (1)所有没有加class的
<h2>
看起来都一样; - (2)具有"category"class(称为mixin)的所有元素看起来都一样;
- (3)当你想让
.myObject h2
看起来跟<h2>
一样,你不需要通过覆盖样式来实现。
如:
<div class="content">
<ul>
<li>吴彦祖</li>
<li>张学友</li>
</ul>
</div>
.content ul {
color: red;
background: blue;
border: 1px solid yellow;
}
没有分离容器和内容,OOCSS并不推荐这样。可改为:
<div class="content">
<ul class="starList">
<li>吴彦祖</li>
<li>张学友</li>
</ul>
</div>
.content {}
.starList {
color: red;
background: blue;
border: 1px solid yellow;
}
区分容器和内容也意味着我们可以抽象出可重用的样式,组建组件库。这样可以在组件库中寻找可复用的元素组装页面。
优缺点
优点:
- 重用性强。可重用的代码必定伴随着减少代码量的特点以及加快开发速度的优势,因此也能提升页面的性能。
- 可维护性强。修改部分样式只需修改局部样式,全局样式的调整只需要修改一次公共“对象/组件”即可。
- 可拓展性强。如相同布局下的模块,添加样式只需要在原有的基础上增加新的皮肤即可。
缺点:
- (最大问题)容易增加CSS体积导致浪费;
- 适合中大型项目,小型项目可能适得其反;
- CSS对象/组件的整理和归档需要一定时间成本;
BEM
BEM也有着OO的思想,在国内深得人心。其背后的想法是将用户界面划分为独立的块,这使得界面开发变得简单和快速。即使是在复杂的用户界面中,它也允许在不复制和粘贴的情况下重用现有代码。官网地址:https://en.bem.info/methodology/quick-start/
BEM代表块(Block),元素(Element),修饰符(Modifier)。
命名规则:
block-name__elem-name_mod-name_mod-val
Block
可重用的功能独立的页面组件。在HTML中,块由className表示。如果代码的一部分可以重用,并且不依赖于正在实现的其他页面组件的话,那么这部分可以命名为块。
特征:
- 块名描述了它的用途(“它是什么?”-menu或button),而不是它的状态(“它是什么样子?”-red或big)。
- 块不应影响其环境,这意味着您不应设置块的外部几何模型(边距)或位置。
- 一个块必须有一个唯一的名字(类),这样才能保证块的独立性。
- 在使用BEM时,也不应该使用CSS标签标记或id选择器。
块可以任意数量嵌套。 如
<!-- `header` block -->
<header class="header">
<!-- Nested `logo` block -->
<div class="logo"></div>
<!-- Nested `search-form` block -->
<form class="search-form"></form>
</header>
Element
元素是块的一部分,没有独立的含义,并且在语义上与其块相关联。如果没有父实体(块),代码段就不能单独使用,那么这部分可以命名为元素。例外情况是,为了简化开发,必须将元素分为较小的部分(子元素)。在BEM方法中,不能创建元素的元素。在这种情况下,您需要创建一个服务块,而不是创建一个元素。
特征:
- 元素名称描述了其用途(“这是什么?”-item、text等),而不是其状态(“什么类型,或它看起来是什么?”-red、big等)。
- 元素全名的结构是
block-name__element-name
,元素名与块名之间用双下划线分隔__
。 - 元素始终是块的一部分,而不是另一个元素。这意味着元素名不能定义诸如
block__elem1__elem2
这样的层次结构。
Modifier
修饰符是块或元素上的标志。用它们来改变外观或行为。
特征:
- 修饰符名称描述了它的外观(“多大尺寸?”或者“哪个主题?”等等——size_s或theme_islands),它的状态(“它与其他有什么不同?”-disabled、focused等)及其行为(“行为如何?”或者“它如何响应用户?”-如left-top)。
- 修饰符名称与块或元素名称之间用一个下划线分隔(
_
)。 - 从BEM的角度来看,修饰符不能与修改后的块或元素隔离使用。修饰符应该更改实体的外观、行为或状态,而不是替换它。
优缺点
优点:
- 包含OOCSS的各项优势;
- 更清晰的样式分离以及命名规范;
缺点:
- 命名容易冗长;
- 只适合较大的样式模块或组件;
同样的另一套写法策略SUIT,它在BEM的基础上进行了改进,增强了块及元素的可读性。因为总体思路类似,在此便不单独介绍,有兴趣可访问官网:https://suitcss.github.io/
M-CSS
Module模块是一个功能相对独立且完整的结构体,这也是组件的概念。这里只从CSS的范围内来探讨模块化,那么CSS模块的定义就可以缩窄到:一个(组)样式相对独立且完整的样式类。
(本人倾向于将下面的MCSS、SMACSS、ITCSS、Atomic Design(Atomic CSS)、FUN归为M-CSS。)
SMACSS
官网地址:http://smacss.com/
SMACSS有着明确的模块划分,它把项目样式拆成如下5个部分:
- Base:基本规则(Base rules),网址的主要元素设置样式, 如body, input, button, ul, ol等。在这一步中, 我们主要使用HTML标签和属性选择器,在特殊情况下,才使用CSS类;
- Layout:布局规则(Layout rules),主要是些全局元素、顶部、页脚、边栏等模块的大小。SMACSS建议使用ID选择器,因为这些模块不太可能在同一个页面上出现多次。
- Modules:模块规则(Modules rules),模块(类似于卡片布局)可以在一个页面中使用多次。对于模块CSS类,不建议使用ID和tag选择器(这是为了方便重用以及上下文独立)。
- State:状态规则(State rules),在这一步中,规定了模块的各种状态以及网站的基础部分。这是唯一允许使用"!important"的地方.
- Theme:主题规则(Theme rules),设计您可能需要更换的样式.
同样的另一套写法策略CCSS,它结合了BEM以及SMACSS,在此不单独介绍,有兴趣可访问官网:http://sathify.github.io/CCSS/
MCSS
官网地址:http://operatino.github.io/MCSS/
MCSS指的是多层CSS(Multilayer CSS)。这种样式写法建议将样式分成多个部分,每个部分称为层(layers)。
- 第0层或基础(Zero layer or foundation):该层负责重置浏览器样式的代码(如: reset.css或者normalize.css);
- 基层(Base layer):该层包括可重用元素的样式: buttons, input等等;
- 项目层(Project layer):该层包括单独的模块和"上下文" - 根据用户端浏览器或用于浏览的设备,用户权限等对元素的样式进行调整;
- 装饰层(Cosmetic layer):该层使用OOCSS风格来书写样式, 对元素外观做微小的调整。MCSS建议仅留下影响外观的风格,而不能破坏网站的布局(例如颜色和非关键缩进等)。
层与层之间的交互层次是非常重要的:
- 在基层(Base layer)中定义中性的样式, 并且不影响其它层.
- 基层(Base layer)中的元素只能影响基层的CSS类.
- 项目层(Project layer)中的元素可以影响基层和项目层.
- 装饰层(Cosmetic layer)是以描述性OOCSS类("atomic"类)的形式进行设计, 不会影响其他CSS代码, 而是在标记中有选择的使用。
ITCSS
官网地址:https://itcss.io/
ITCSS将整个CSS项目想象成一个分层的、倒置的三角形。这个分层图形表示了一个能帮你以有效和节约的方式组织CSS的模型。 在ITCSS中,每一层都是上一层的逻辑递进。它使样式开发变得更加明确、有目的性,并缩小了选择器的使用范围。
下面是ITCSS的层级:
- 设置层(Settings):该层包含了项目中的所有全局设置(如基本字体大小、颜色调色板、配置等)。
- 工具层(Tools):该层包含了全局可用的工具–即混入和函数。全局工具的例子有梯度混入、字体大小混入等。
- 通用层(Generic):该层是第一个实际产生CSS的层。它很少改动,并且通常在不同项目中保持不变。它包括像Normalize. css、全局盒子大小规则、CSS重置等。
- 元素层(Elements):该层只包含没有样式的html元素选择器。
- 对象层(Objects):该层是第一个包含了基于类的选择器的层。
- 组件层(Components):该层开始设计有可识别性的DOM元素。
- 核心层(Trumps):该层是具有最高特异性的层,可以覆盖之前的样式。该层大部分声明都带有
!important
。
Atomic Design (Atomic CSS, ACSS)
官网地址:https://aCSS.io/
主张一切都是原子组件构造而成,它将样式分为以下几个部分:
- 原子(Atom):每一个HTML元素就是一个原子,比如一个input。
- 分子(Molecules):原子组成分子,分子是一堆原子组件得到的东西,比如说一个输入框,它由label、input、还有个提交button。
- 生物体(Organisms):分子组成生物体,生物体是一堆分子组合而成的,比如网站的一个header里面包含了搜索框、导航栏、中户中心等等。
- 模版(Templates):模版也就是把各种东西组合起来之后的布局等,基本上整个站点的结构都已经成型了。
- 页面(Pages):就是加上图片等等其他的乱七八糟的东西,得到了最终的页面。
其组成结构如下:
类似inline style的写法
用Atomic CSS写出来的html就像这样:
<div class="Bgc(#0280ae) C(#fff) P(20px)">
刘德华
</div>
眼熟不,这也是它的一大优势,让你用起来非常顺手,就像写inline的style一样,对于写出基于视觉功能的小的, 单用途CSS类无疑是最易懂和最方便的。
<div style="background-color: #0280ae; color: #fff; padding: 20px">
刘德华
</div>
CSS modules
官网地址:https://github.com/css-modules/css-modules
与上述介绍的各种CSS写法不同,CSS Modules既不是一个CSS官方的规范,也不是浏览器的一种机制,它是一种构建步骤中的一个进程。(构建通常需要webpack或者browserify的帮助)。通过构建工具的帮助,可以将className或者其他选择器的名字作用域化。(类似命名空间化。)
它的使用如下例:
import styles from "./styles.css";
element.innerHTML =
`<h1 class="${styles.title}">
An css modules example
</h1>`;
它生成的节点:
<!-- className变成了hash值 -->
<h1 class="2n0Msd2op5">An css modules example</h1>
可以看出,css modules既解决了className命名冲突的问题,也解决了样式全局作用污染的问题。
优缺点
优点:
- 生成了唯一className,解决了全局命名冲突以及样式全局污染的问题;
- 实现样式模块化、组件化;
- 解决嵌套层次过深的问题,只使用一个className就能把所有样式定义好;
缺点:
- 同Less、Sass类似,一些非css的语法有一定上手成本
- 与组件库难以配合,导致最后出现大量的
:global
CSS in JS
官网地址:https://speakerdeck.com/vjeux/react-css-in-js
CSS in JS也不是一个官方的规范或浏览器的一种机制,它是一种构建步骤中的一个进程。顾名思义,在js中写css,这也是React所推荐的all in js思想。随着React的日益壮大以及组件化思想的深入人心,这种"关注点混合"的新写法逐渐成为主流。
用来加强JS对CSS操作的工具统称为CSS in JS。以下是几个主流的CSS in JS工具:styled-components、emotion、jss、radium、glamorous、polished。更多的工具可参考MicheleBertoli/css-in-js,他们使用方法类似,其中styled-components可使用单元测试工具jest-styled-components。
它的使用如下例(styled-components):
const FooterStyle = styled.footer`
padding: 40px;
color: #4e6e8e;
`
let Footer = (
<FooterStyle>
CSS in JS
</FooterStyle>
)
它生成的节点:
<!-- className变成了hash值 -->
<footer class="hPxfNo">An CSS in JS example</footer>
可以看出,CSS in JS既解决了className命名冲突的问题,也解决了样式全局作用污染的问题。并且CSS in JS写样式如同写JSON,适合那些没有css运行环境的平台,如react-native。
优缺点
优点:
- 比起Less/Sass等预处理工具,CSS in JS使用js语法,不用从头学习一套专用的API;
- 适用于react-native等没有css运行环境的开发;
- 跟css modules一样,因为生成了唯一className,解决了全局命名冲突以及样式全局污染的问题;
- js和CSS之间可以变量共享,比如一些基础的颜色和尺寸,方便样式的管理操作;
- 样式按需加载,只生成页面需要用到的代码,提升了性能;
缺点:
- 样式缺乏拓展性;
- 编辑器不够友好(高亮及提示);
- 完全违背了"关注点分离"的原则,增加了复杂度,且部分开发人员会对此不习惯;
- 对前端框架确实有依赖性,更适合于组件化的框架,如React;