Moo-CSS:模块化面向对象的CSS(初稿)

组件模块化思想 + OOP。

前言

在某个需求中,你遇到了某个设计稿其中底部一部分如下:
p-leader-1

简单得没朋友,你用一分钟时间写完了html及CSS样式:

1
2
3
4
<footer class="footNote">
23年专业品质,让投资更简单<br/>
过往业绩不预示未来表现,投资有风险,入市需谨慎
</footer>

1
2
3
4
5
6
7
.footNote {
padding: 20px 0;
line-height: 2;
font-size: 12px;
text-align: center;
color: #999;
}

又过了几天,你又遇到了某个设计稿其中底部一部分如下:
p-leader-2

唉?似曾相识的样式,返回之前的样式一看发现不能完全复用,于是复制了之后改了改样式:

1
2
3
4
<footer class="footNote">
过往业绩不预示其未来表现,市场有风险,投资需谨慎<br/>
投资人请详阅<a>《资产管理合同》</a>,并自行承担投资风险
</footer>

1
2
3
4
5
6
7
8
9
10
.footNote {
padding: 50px 0;
line-height: 1.5;
font-size: 12px;
text-align: center;
color: #ccc;
}
.footNote a {
color: #01a2fc;
}

接着又过了几天:
p-leader-3

。。。

在看完本文后,你或许会这样写:

1
2
3
4
<footer class="u-pb20 g-lh200 g-fs24 f-tc g-mt20" s-ft_sub_>
23年专业品质,让投资更简单<br/>
过往业绩不预示未来表现,投资有风险,入市需谨慎
</footer>

1
2
3
4
<footer class="u-pb50 g-lh150 g-fs24 f-tc g-mt50" s-cr_gray>
过往业绩不预示其未来表现,市场有风险,投资需谨慎<br/>
投资人请详阅<a class="f-cr_blue">《资产管理合同》</a>,并自行承担投资风险
</footer>
1
2
3
4
<footer class="u-pb20 g-lh200 g-fs24 f-tc g-mt20" s-cr_gray>
过往业绩不预示其未来表现,市场有风险,投资需谨慎<br/>
投资人请详阅<a f-cr_blue>《资产管理合同》</a>,并自行承担投资风险
</footer>

而要写的CSS呢?none

为何不用写CSS?这些g-mt20 u-pb20 g-lh200 g-fs24 f-tcs-cr_gray又是什么意思?这就是接下来本篇想要将的内容。

抛出问题

在日常CSS的编写(包括使用预处理工具)中,我们经常会遇到如下几个主要问题:

  • 难以复用:很多样式相似,但往往就差那么点边距,差点大小跟字体颜色,然后就需要c/v操作后再修改了,难以直接复用。
  • 命名冲突:在复杂项目中,有时候会出现命名冲突,容易导致样式错乱的情况,并且这种情况较难debug;
  • 选择器及层级结构混乱:为了避免样式冲突,有些人会使用多层选择器来保证样式的依赖,但往往导致选择器层级过高而较难维护,并且导致样式权重过高而难以调整。

这些问题在复杂性项目,特别是多人协作项目中尤为明显。本篇旨在解决上述问题,更好得开发CSS。

浏览器的CSS解析

合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。 ——《老子》

欲更好地写CSS,我们首先需要实现了解我们写的CSS是如何被浏览器解析的。

浏览器结构与呈现引擎

browser layers

浏览器的主要组成如下:

  • 用户界面(User Interface) - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。
  • 浏览器引擎(Browser Engine) - 在用户界面和呈现引擎之间传送指令。
  • 呈现引擎(Rendering Engine) - 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
  • 网络(Networking) - 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。
  • 用户界面后端(UI Backend) - 用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。
  • JavaScript 解释器(JavaScript Interpreter)。用于解析和执行 JavaScript 代码。
  • 数据存储(Data Persistence)。这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。

CSS的解析和渲染便由其中的呈现引擎来实现。呈现引擎将开始解析HTML文档,并将各标记逐个转化成“内容树”上的DOM节点。同时也会解析外部CSS文件以及样式元素中的样式数据。HTML中这些带有视觉指令的样式信息将用于创建另一个树结构:呈现树
呈现树包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序就是它们将在屏幕上显示的顺序。

呈现树构建完毕之后,进入“布局”处理阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标。
下一个阶段是绘制 - 呈现引擎会遍历呈现树,由用户界面后端层将每个节点绘制出来。

需要着重指出的是,这是一个渐进的过程。为达到更好的用户体验,呈现引擎会力求尽快将内容显示在屏幕上。它不必等到整个HTML文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,呈现引擎会将部分内容解析并显示出来。

browser flow

CSS的解析

CSS是一种“与上下文无关”的语法,W3C给出的CSS语法规则(https://www.w3.org/TR/CSS2/grammar.html)如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ruleset
: selector [ ',' S* selector ]*
'{' S* declaration [ ';' S* declaration ]* '}' S*
;
selector
: simple_selector [ combinator selector | S+ [ combinator? selector ]? ]?
;
simple_selector
: element_name [ HASH | class | attrib | pseudo ]*
| [ HASH | class | attrib | pseudo ]+
;
class
: '.' IDENT
;
element_name
: IDENT | '*'
;
attrib
: '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
[ IDENT | STRING ] S* ] ']'
;
pseudo
: ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ]
;

举个例子,

1
2
3
4
5
6
div, p {
margin-top: 3px;
}
.error {
color:red;
}

div, p 和 .error 是选择器。大括号内的部分包含了由此规则集应用的规则。此结构的正式定义是这样的:

1
2
3
4
ruleset
: selector [ ',' S* selector ]*
'{' S* declaration [ ';' S* declaration ]* '}' S*
;

那么它会被解析成什么呢?

CSS analysis

  • 在这里我们也可知道,越少的CSS语句,浏览器需解析的代码量越少,其需要花在解析CSS的时间就越少,呈现效果越快;同样越简洁的CSS语句也是如此。虽然这可以说是句废话。

CSS的匹配

解析了CSS后,如何进行样式匹配呢,这就是接下来的内容

*Gecko的规则树

所有匹配的规则都存储在规则树中。路径中的底层节点拥有较高的优先级。规则树包含了所有已知规则匹配的路径。规则的存储是延迟进行的。规则树不会在开始的时候就为所有的节点进行计算,而是只有当某个节点样式需要进行计算时,才会向规则树添加计算的路径。
在计算某个特定元素的样式上下文时,我们首先计算规则树中的对应路径,或者使用现有的路径。然后我们沿此路径应用规则,在新的样式上下文中填充结构。我们从路径中拥有最高优先级的底层节点(通常也是最特殊的选择器)开始,并向上遍历规则树,直到结构填充完毕。如果该规则节点对于此结构没有任何规范,那么我们可以实现更好的优化:寻找路径更上层的节点,找到后指定完整的规范并指向相关节点即可。这是最好的优化方法,因为整个结构都能共享。这可以减少端值的计算量并节约内存。

webkit匹配规则

在WebKit中没有规则树,因此会对匹配的声明遍历4次。首先应用非重要高优先级的属性(由于作为其他属性的依据而应首先应用的属性,例如display),接着是高优先级重要规则,然后是普通优先级非重要规则,最后是普通优先级重要规则。这意味着多次出现的属性会根据正确的层叠顺序进行解析。最后出现的最终生效。

样式表解析完毕后,系统会根据选择器将CSS规则添加到某个哈希表中。这些哈希表的选择器各不相同,包括ID、类名称、标记名称等,还有一种通用哈希表,适合不属于上述类别的规则。如果选择器是ID,规则就会添加到ID表中;如果选择器是类,规则就会添加到类表中,依此类推。
这种处理可以大大简化规则匹配。我们无需查看每一条声明,只要从哈希表中提取元素的相关规则即可。这种优化方法可排除掉95%以上规则,因此在匹配过程中根本就不用考虑这些规则了。

1
2
3
p.error {color:red}
#messageDiv {height:50px}
div {margin:5px}
1
2
<p class="error">an error occurred </p>
<div id="messageDiv">this is a message</div>

我们首先会为 p 元素寻找匹配的规则。类表中有一个“error”键,在下面可以找到“p.error”的规则。div 元素在 ID 表(键为 ID)和标记表中有相关的规则。剩下的工作就是找出哪些根据键提取的规则是真正匹配的了。
例如,如果 div 的对应规则如下:

1
table div {margin:5px}

这条规则仍然会从标记表中提取出来,因为键是最右边的选择器,但这条规则并不匹配我们的 div 元素,因为 div 没有 table 祖先。

在这里我们也可以知道写CSS的时候还可以从以下两个方面:避免直接用标签写CSS样式,推荐用类(id不可复用);尽量避免CSS层级过多,不仅便于查找匹配,并且对样式层级计算的排序也有好处。

为什么讲这一部分呢?因为接下来模块化以及面向对象的css写法也极大得应用了刚总结出的优化经验。

OO-CSS概念

在js中,面向对象编程的抽象化、模块化和继承等特点在编写代码中得到了大规模的应用。CSS虽然不像js那样可以通过显性对象来开发,但我们也可以利用OOP的思维,抓住“对象”的核心来更好得编写和维护项目的CSS,这就是OO-CSS(Object-Oriented CSS)。由此我们也可以知道,OO-CSS并不是一种框架、技术甚至是一种新的语言,而是一种编写CSS时的一种思维方式。

oo-CSS的概念提出已久(官网:http://oocss.org/)。

那么接下来描述下如何进行OO-CSS的开发。

CSS“对象”

OOP编程,模块以对象为单位。所以首先我们要确定CSS“对象”。在css的概念里,CSS“对象”是一个可重复的视图。

CSS“对象”由以下四部分组成:

  • HTML,可以是DOM的一个或多个节点;
  • CSS声明,关于这些DOM节点样式的CSS声明,所有这些节点都以包装节点的类名开头
  • 组件,如背景图片,sprites等用于展示资源的
  • 与对象关联的javascript行为、侦听器或方法。

这可能会令人困惑,因为每个CSS类不一定是其自身权利的对象,但可以是包装类的属性。
如:

1
2
3
4
5
6
7
<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>

1
2
3
.mod .inner {}
.mod .inner .hd {}
...

在这里,mod是一个CSS“对象”,它包含四个属性节点(不能独立于模块生存,包括两个必需的区域:内部和主体,以及两个可选的区域:头部和脚)。

两大原则

分离结构和皮肤

分离结构和皮肤意味着要将重复的样式特征(如背景和边框等样式)定义为单独的“皮肤”,通过和其他各种CSS“对象”的混合及匹配,使得在没有太多代码的情况下实现大量的视觉变化。

比如说一个渐变按钮,那么 .btn 的 class 是不会包含渐变相关的属性的,而是需要单独抽取出一个渐变的 class,然后让 .btn 去扩展从而得到一个渐变的按钮。

可以看下面这个例子:

1
<div class="content">吴彦祖</div>

1
2
3
4
5
6
7
8
.content {
margin: 20px auto;
width: 150px;
height: 150px;
color: red;
background: blue;
border: 1px solid yellow;
}

没有分离结构与皮肤,not good。可改为:

1
<div class="content skin-blue">吴彦祖</div>

1
2
3
4
5
6
7
8
9
10
.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>一样,你不需要通过覆盖样式来实现。

如:

1
2
3
4
5
6
<div class="content">
<ul>
<li>吴彦祖</li>
<li>张学友</li>
</ul>
</div>

1
2
3
4
5
.content ul {
color: red;
background: blue;
border: 1px solid yellow;
}

没有分离容器和内容,not good。可改为:

1
2
3
4
5
6
<div class="content">
<ul class="starList">
<li>吴彦祖</li>
<li>张学友</li>
</ul>
</div>

1
2
3
4
5
6
.content {}
.starList {
color: red;
background: blue;
border: 1px solid yellow;
}

区分容器和内容也意味着我们可以抽象出可重用的样式,组建组件库。这样可以在组件库中寻找可复用的元素组装页面。

其他建议

提取可继承的共性样式在父类中


1
2
3
4
5
6
7
8
9
/* not good */
.mod .inner-top { color: red; font-size: 24px; }
.mod .inner-middle { color: red; font-size: 26px; }
.mod .inner-bottom { color: blue; font-size: 24px; }

/* good */
.mod { color: red; font-size: 24px; }
.mod .inner-middle { font-size: 26px; }
.mod .inner-bottom { color: blue; }

扩展样式尽可能在要拓展样式的元素本身添加class而不是它的父类。

如上例

减少在对象中写位置相关的样式


1
2
3
<header class="container">...</header>
<section class="container">...</section>
<footer class="container">...</footer>

1
2
3
header.container { position: fixed; top: 0; left: 50%; width: 100px; height: 100px; }
section.container { position: fixed; top: 200px; left: 40%; width: 100px; height: 100px; }
footer.container { position: fixed; bottom: 0; left: 30%; width: 100px; height: 100px; }

可改为

1
2
3
<header class="container header">...</header>
<section class="container section">...</section>
<footer class="container footer">...</footer>

1
2
3
4
container { width: 100px; height: 100px; }
.header { position: fixed; top: 0; left: 50%; }
.section { position: fixed; top: 200px; left: 40%; }
.footer { position: fixed; bottom: 0; left: 30%; }

尽量保证选择器权重相同


1
2
3
<div class="container">
<h1 class="tit" cr_red>郭富城</h1>
</div>

1
2
[cr_red] { color: red }
.tit { color: #323232; }

由于选择器权重导致样式无法设置,可改为

1
2
3
<div class="container" cr_base>
<h1 class="tit cr_red">郭富城</h1>
</div>

1
2
[cr_red] { color: red }
[cr_base] { color: #323232; }

OO的优缺点

优点

同OOP的特点一样,cssP也有着如下优点:

  • 重用性强。可重用的代码必定伴随着减少代码量的特点以及加快开发速度的优势,因此也能提升页面的性能。
  • 可维护性强。修改部分样式只需修改局部样式,全局样式的调整只需要修改一次公共“对象/组件”即可。
  • 可拓展性强。如相同布局下的模块,添加样式只需要在原有的基础上增加新的皮肤即可。

缺点(风险点)

  • (最大问题),容易增加CSS体积导致浪费;
  • 适合中大型项目,小型项目可能适得其反;
  • 增加了HTML的体积,且容易导致HTML可读性较差;
  • 有一定学习成本/上手成本;
  • CSS对象/组件的整理和归档需要一定时间成本;

其中容易增加CSS体积这点主要体现在当你修改某一个具体的元素的样式的时候, 大部分情况下,除了修改CSS本身(因为多数的CSS类是通用的), 你还不得不添加更多的标记类。

M-CSS

Module模块是一个功能相对独立且完整的结构体,其实这应该是组件的概念,我们这里只从CSS的范围内来探讨模块化,那么模块的定义就可以缩窄到:一个(组)样式相对独立且完整的类

模块化的目的也是为了更好得复用和维护,它所遵守的SRP原则即每个模块或者类只应承担软件系统中的某个单一功能,并且该职责应该完整地封装在类的内部,即对外屏蔽内部实现。而具体到CSS的领域里,SRP意味着某个代码片、类或者模块只应该做一件事。

模块化 CSS 需要你换一个角度看问题,不从页面级别考虑,而是关注组成页面的小块。这不是一个页面而是一个组件的集合。(自下而上的思维)

先简单介绍几个较火的模块化CSS写法

SMACSS

官网:https://smaCSS.com/

SMACSS把CSS样式拆成如下5个部分:

  • Base:基本规则(Base rules),为网址的主要元素设置样式, 如body, input, button, ul, ol等. 在这一步中, 我们主要使用HTML标签和属性选择器, 在特殊情况下, 使用CSS类(如: 如果您有Java-Style选择);
  • Layout:布局规则(Layout rules),主要是些全局元素, 顶部, 页脚, 边栏等模块的大小. Jonathan建议使用ID选择器, 因为这些模块不太可能在同一个页面上出现多次. 然而, 本文作者认为这是个很不好的习惯(每当ID出现在样式文中, 感觉世界顿时变得灰暗, 有一股莫名的哀伤).
  • Modules:模块规则(Modules rules),模块(类似于卡片布局)可以在一个页面中使用多次. 对于模块CSS类, 不建议使用ID和tag选择器(这是为了方便重用以及上下文独立).
  • State:状态规则(State rules),在这一步中, 规定了模块的各种状态以及网站的基础部分. 这是唯一允许使用”!important”的地方.
  • Theme:主题规则(Theme rules): 设计您可能需要更换的样式.

sma-CSS

另一套CSS写法MCSS也有着明确的分层思想:基础(Zero layer or foundation)、基层(Base layer)、项目层(Project layer)、装饰层(Cosmetic layer),本部分只作引荐分层的思想,故不作对MCSS的单独介绍

特别的是,SMACSS 有提出自身的命名规则。基础类别中样式一般使用元素类型选择器,用来规范元素的初始样式。布局类别中的样式一般使用“l-”作为前缀(layout)。状态类别中的样式一般使用“is-”作为前缀(如is-hidden)。而对于不同的模块,则使用模块的名称作为前缀。

Atomic Design (Atomic CSS)

https://aCSS.io/
为了组件化的 CSS 而设计的产物。

一切都是原子组件构造而成。

  • 原子(Atom):每一个 HTML 元素就是一个原子。比如一个 input;
  • 分子(molecules):原子组成分子,就是一堆原子组件得到的东西,比如说一个输入框,前面有个 label,中间有个input,最后还有个提交 button。
  • 生物体(Organisms):分子组成生物体,一堆分子组合而成的,最简单的例子就是网站的一个 header 里面包含了搜索框、导航栏也许还有用户中心什么的。
  • 模版(Templates):模版也就是把各种东西组合起来之后的布局等,基本上整个站点的结构都已经成型了
  • 页面(Pages):就是加上图片等等其他的乱七八糟的东西,得到了最终的页面

用Atomic CSS写出来的html就像这样

1
2
3
<div class="Bgc(#0280ae) C(#fff) P(20px)">
刘德华
</div>

眼熟不,这也是它的一大优势,让你用起来非常顺手就像 inline 的 style 一样,对于写出基于视觉功能的小的, 单用途CSS类无疑是最易懂和最方便的。

1
2
3
<div style="background-color: #0280ae; color: #fff; padding: 20px">
刘德华
</div>

atormicCSS

当然,它的缺点也很明显:

  • CSS类名是属性名称的描述,而不是元素的自然语义,使得开发本身也十分容易复杂化。
  • 直接在HTML中进行显示设置。

写出基于视觉功能的小的, 单用途CSS类无疑是最快最方便的,这便是我们要借鉴的。

FUN

  • F, 选择器的扁平的层次结构: 建议使用CSS类选择元素(items), 避免不必要的级联, 杜绝使用id.
  • U, 实用(功能)样式: 鼓励创建原子(atomic)样式来解决典型的修正(微调)任务, 例如: w100表示width: 100%; 或者fr表示float: right;
  • N, 名称分割组件: Ben建议添加命名空间来指定特定模块元素的样式. 这种方法将避免类的中重叠.

FUN也有着Atomic CSS的一丝意味,虽不如AtomicCSS语义那么明确,但更加简便。

AMCCSS

官网:http://amCSS.github.io/

AMCCSS推荐用属性来写样式,这也避免了复杂的class导致可读性变差。但是部分属性在当今使用了虚拟DOM的主流框架上易显得臃肿和难以控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- Large primary button -->
<a class="btn btn-primary btn-lg">Large primary button</a>
<a am-Button="primary large">Large primary button</a>

<!-- Default button -->
<a class="btn btn-default">Default button</a>
<a am-Button>Default button</a>

<!-- Small info button -->
<a class="btn btn-info btn-sm">Small info button</a>
<a am-Button="info small">Small info button</a>

<!-- Extra-small danger button -->
<a class="btn btn-danger btn-xs">Extra-small danger button</a>
<a am-Button="danger extra-small">Extra-small danger button</a>

在当今人人专注于用类来写CSS的时候,我们也可以适当利用下属性,那么什么时候可以用属性来写样式呢,可见后文。

正如你所看到的, 这些方式中都有优势和明确的特点,但没有哪一个是非常理想的。 因此, 并没有对所有人所有项目都适合的绝对的标准 - 你可以从这些方法中选择某一个开始, 创建某些属于你自己的东西. 亦或者完全重头开始建立一套你自己的标准。

接下来介绍的Moo-CSS便是本人根据自身业务总结整理的一套标准。适用于有设计规范但无法像后台管理系统般样式复用性强的需求(2C)。

Moo-CSS

github Moo-CSS:https://github.com/MichealWayne/Moo-CSS

顾名思义,Moo-CSS结合了oo-CSS和m-CSS的主要优势,命名模块化的面向对象的CSS写法。适合2C的各类型的样式开发。

  • 优点:
    • 重用性强
    • 维护性强
    • 拓展性强
  • 缺点:
    • 上手成本~

M(模块化)

Moo-CSS的模块化主要体现在样式分类的模块化以及样式层级的模块化。

1.1 样式分类

根据样式属性的特征,将样式分类为以下模块:

  • reset:重置。重置浏览器默认样式;
  • grid:布局。布局位置相关样式。包含样式属性:margin, position, line-height等;
  • module:模块。业务模块,头、导航、菜单、列表等等;
  • function:功能。溢出隐藏等功能性样式;包含样式属性:clear, text-align, overflow, font-style, font-weight, font-family, vertical-align, white-space, text-decoration, text-indent等;
  • unit:单元。宽高,padding等影响块或元素的常用单元样式;包含样式属性:width, height, padding, display, border, flex等;
  • component:组件。图标,蒙层等常用轻量样式组件;
  • status:状态。透明度、是否隐藏、层级等显示状态样式(是规定唯一可设置!important的部分);包含样式属性:visibility, opacity, z-index, transform等;
  • skin:皮肤。主题颜色背景色等;包含样式属性:color, background-color, box-shadow等;
  • animation:动画。过渡和动画。包含样式属性:animtaion, transition。

另外两种特殊模块:

  • JavaScript DOM:DOM操作。供js操作DOM节点,不作样式使用
  • React/Vue/Angular sepcial:框架独有。供专有框架使用,如过渡动画。

其中grid, module, unit, component, status, animation通常由类(class)实现,skin通常由属性(attribute)实现。function大部分由类实现,部分由属性实现。

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/* reset */
* {
margin: 0;
padding: 0;
border: 0;
}
a,
a:hover {
text-decoration: none;
}

/* module */
.menu {
width: 100%;
height: 100px;
overflow-y: auto;
}
.menu li {
heigth: 60px;
line-height: 60px;
}

/* grid */
.pr {
position: relative;
}
.mt10 {
margin-top: 0.133rem;
}

/* function */
.unl {
text-decoration: underline;
}
.ellipsis {
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
overflow: hidden;
}
.tc {
text-align: center;
}


/* unit */
.w10{
width: 10px;
}
.h10{
height: 10px;
}
.pt30 {
padding-top: 30px;
}

/* component */
.btn{
padding: 0;
text-align: center;
cursor: pointer;
border-radius: 4px;
}
.win_bg{
position: fixed;
z-index: 18;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100%;
}
.icon-circle {
width: 6px;
height: 6px;
border-radius: 50%;
}

/* status */
hide {
display: none !important;
}
.index_999{
z-index: 999;
}
.hide_60per {
opacity: 60;
filter: alpha(opacity=60);
}

/* animation */
.fadein {
animation: fadein 1s 1 ease;
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}

/* theme */
.bgc_red {
background-color: red;
}

[cr-blue] {
color: blue;
}
.icon-circle[big] {
width: 60px;
height: 60px;
}

1.2 层级分类

根据样式属性的特征,将项目样式分层为以下模块层级:

  • Base:基础层,样式最底层,包含样式重置reset以及极常出现的布局及单样式、展示状态。(通常所有页面共用且不做修改)。
  • Component:组件层,包含样式组件和方法组件,简单组件样式,如按钮、蒙层;方法组件包括动画方法和预处理方法如rem单位设置、背景图片设置。可依赖于Base层和Skin层。
  • Skin:皮肤层,业务中常出现的颜色,背景色,且提供预处理的颜色变量。常供应于Component层和Module层;
  • Module:模块层,根据业务划分的模块,依赖于上面几个模块;
  • Layout: 结构层,提供Module层布局样式,构成最终页面。

mcss

也就是说,页面是由Base、Component、Skin、Layout这四个层级辅助Module层完成样式开发。

那么1.1的样式在这几层里如何归类呢?

多页面:

  • Base:reset、unit(高权重)、function(高权重)、status(高权重)、component(高权重)、theme(高权重),theme attribute(高权重)、grid(高权重)
  • Component:component,animation、function
  • Skin:skin
  • Layout: grid
  • Module:页面私有module、component

*其中Base的样式,其样式属性不建议超过5个标签;Component的选择器层级不建议超过2层,Module的选择器层级不建议超过4层。

1.3 样式权重计算

公式

1
1 / (样式资源量 / 样式属性耦合度 * 0.4 + 0.3 / 样式使用率 ^ 2 + 选择器权重 * 0.3)

数值越大权重越高,高权重可归入Base层。

其中,样式资源量可由样式代码量和引入资源大小进行衡量;样式属性耦合度是指在多样式属性的样式中,属性直接的耦合度,如溢出省略’…’样的耦合度就非常高,单属性为1;样式使用率主要考虑多页面(路由)的样式使用率;选择器权重计算值越小越好。权重公式仅做参考

2 OO(面向对象)

2.2 OO特征

2.2.1 封装

按1.1中样式模块分类封装,保持模块之间的独立性

2.2.2 继承

Component层可由页面Module层进行样式继承和拓展

2.2.3 多态

Base层样式可拼接成多种模块容器,这些模块容易包含原有的样式多态性。

命名

既然模块和对象的归类和分层已经完成了,那么接下来的主要问题就是在于如何给样式类/属性进行命名了。

命名的核心:Base层、Skin层、Layout层参照FUN、Components、Module使用BEM;所有类/属性的归类部分参照SMACSS和NEC(http://nec.netease.com/standard/CSS-sort.html)。

类名或属性名由小写字母,_-符号组成

类或属性的写法为:

1
2
3
4
5
	标志-类/或属性-字母值
```

```
标志-类/属性数字值

其中标志由样式模块确认:

  • g-:grid
  • m-: module
  • f-: function
  • u-: component, unit
  • z-: status
  • s-: skin

  • a-: animation

  • j-: JavaScript DOM

属性简写,如margin-top -> mtbackground-color -> bgc,属性值单位为px时,省略px;为rem时,数字转为px并省略rem;为%时,则%换为per,
padding-left: 30px -> pl30width: 1rem -> w75left: 50% -> l50per

类名为非模块时,名字为标识,如iconovhidden

结合标志,如下

1
2
3
4
5
.u-w30per
.g-mt30
.f-blod
.s-bgc_red
[s-cr_red]

类名为module时,按照如下命名规则

(业务)module的命名:结合BEM

对于业务模块的命名,个人觉得BEM最为合适。

BEM分别代表块(Block),元素(Element),修饰符(Modifier)。

首先我们可以把一个页面分为多个块,如:头,内容,尾。用来标识一个具体块的关键字其实就是这个块的名字,如:头->head, 内容->content, 导航->nav, 尾->foot。
一个块必须有一个唯一的名字(类),这样才能保证块的独立性。
块由结构来控制其布局。

然后再对块中依赖于块的元素进行划分。用来标识一个元素的关键字也是这个元素的名字。如导航栏链接或菜单的每一项->item


1
2
3
4
<nav class="m-nav">
<a class="m-nav__item">nav 1</a>
<a class="m-nav__item">nav 2</a>
</nav>

我们在长名称中使用连字符分隔单词(例如,block-name),使用两个下划线来分隔块名和元素名(block-name__element-name)。

那么修饰符是用来干嘛的呢?她说为了避免开发一个和现有的块只稍微有点不同的另一个块,作为一个块或是一个元素的一种属性,代表这个块或这个元素在外观或是行为上的改变。
对于一个块或者是一个元素来说修饰符是一个附加的CSS类。

在Moo-css中,修饰符为Base层 + Component层中的function + Skin层。在提供装饰的同时保证了其语义化。


1
2
3
4
<nav class="m-nav f-tc" u-size="big" s-bgc="yellow">
<a class="m-nav__item">nav 1</a>
<a class="m-nav__item nav_type_selected">nav 2</a>
</nav>

这些模块在提高开发速度的同时,也满足了减少CSS语句,选择器层级的建议。

最后再结合Layout层对模块进行布局,如

1
2
3
4
5
6
<section class="g-pr">
<nav class="m-nav f-tc g-pa g-t50l100" u-size="big" s-bgc="yellow">
<a class="m-nav__item">nav 1</a>
<a class="m-nav__item nav_type_selected">nav 2</a>
</nav>
</section>

class顺序为:module Base Component Skin Layout

命名词典可参考:Moo-css 命名词典

现在回到最初的需求,如下写法便不难理解了。

1
2
3
4
<footer class="u-pb20 g-lh200 g-fs24 f-tc g-mt20" s-ft_sub_>
23年专业品质,让投资更简单<br/>
过往业绩不预示未来表现,投资有风险,入市需谨慎
</footer>

4 其他

4.1 主流前端框架中的Component和Module

在使用主流前端框架,如Vue,Module层可根据在路由views中的vue文件中各自定义;Component可在组件components的vue文件中定义,易于区分和维护。
如views/a.vue

1
2
3
4
5
6
<template>
<div class="m-a">...</div>
</template>
<style>
.m-a {}
</style>

components/b.vue

1
2
3
4
5
6
<template>
<div class="u-a">...</div>
</template>
<style>
.u-a {}
</style>

4.2 关于预处理的mixins和skins

mixins和skins通常在项目样式Base层,由于预处理定义的方法跟变量不会影响生成后的CSS体积,因此原则上是越精细越好。

如mixins.less

1
2
3
4
5
6
7
8
9
10
11
12
13
// border-top 1px mobile
.bdt1px(@color) {
position: relative
&:after {
left: 0;
top: 0;
width: 100%;
height: 1px;
-webkit-transform: scale(1, 0.5);
transform: scale(1, 0.5);
background-color: @color;
}
}

module.less

1
2
3
4
5
6
@import 'mixins.less';
.m-nav {
.m-nav__item {
.bdt1px(red);
}
}

FAQ

类名过多?过长?

不会。http://www.stubbornella.org/content/2011/04/28/our-best-practices-are-killing-us/

预处理可以封装,也不会造成浪费,为什么要用这个?

可缓存,可读性。

关于用属性描写样式的看法

用属性来描写样式,一方面是为了减轻大量类描写带来的压力,也体现出了其修饰(装饰)的特征。然而并不尽人意的地方也有,小程序目前并不支持属性选择器。此时需要将Skin层中的属性样式转为类class。

最后

无欲速,无见小利。欲速则不达,见小利则大事不成。以梅超风同学提醒激励下自己。

梅超风

反馈


相关阅读