前端CSS的工程化——掌握Sass这四大特性就够了
初遇 CSS,一见倾心
记得刚遇见css的时候,咱像是见了美人儿一样,简直是爱不释手啊,简简单单写几行算不上代码的代码,就能做出这么漂亮的东西,这也成了咱前端之路的最初动力。
然而,随着项目体量和页面复杂度的增加,咱很快就发现这美人儿非但不漂亮,而且缺胳膊少腿儿的:
- 缺少模块系统。模块系统是软件工程化的基石,CSS 的这个缺陷对前端项目的工程化管理造成了很大阻力,导致开发大型应用时编码和维护都异常困难。js 一开始也没有模块系统,后来各种轮子频出,什么CMD,AMD,UMD全蹦出来了,乱哄哄的,好在 ES6 从语言层面引入了模块系统才终结了这种乱象,以后 js 的模块化终于可以统一了。css 你怎么不好好向你的好基友 js 学习呢,人家都有了,你还傻了吧唧的一点动静都没有。
- 没有变量机制。这对控制多个地方会引用到的属性值很不方便。比如一个颜色值,页面好多地方用到,设计MM突然心血来潮把这个值换成了另一个颜色,咱们怎么办,ctrl+f 全局替换?万一换掉了不该换的,或者漏掉了几个怎么办。
- 嵌套的层级写法非常蛋疼。经常会出现
.page .content .left-side .profile .name{};
.page .content .left-side .profile .age{};
这种看起来很不爽,写起来更不爽的写法。这是程序员最不能忍受的——重复。 - 复用困难。复用是软件工程的核心思想,css 不仅没提供模块系统,而且巧妙地避开了工程化的诸多实践。更加觉得 css 这门语言设计的跟闹着玩儿似的。
- blabla.. 其它的都不是很严重啦。
CSS 没有模块系统,你当我 @import 是空气?
CSS 的 @import 规则是可以在一个 css 文件导入其他 css 文件,但这货需要执行到它时才能触发浏览器去下载它所 import 来的 css 文件,导致页面加载起来特别慢,还不如直接在 里写一大坨 标签的引入效率高,是名副其实的鸡肋功能,演员一般的存在,用你一次算我输。
有需求就会有市场,Sass(Syntactically Awesome Style Sheets) 应运而生。
老规矩,先来看看 Sass 官网的原话:
Sass 是世界上最成熟、最稳定、最强大的专业级 CSS 扩展语言!
这货还真是一点也不谦虚,“最成熟,最稳定,最强大”。
通过这几年项目中的实际运用,咱发现这句简短霸气的描述其实并没有丝毫的浮夸,Sass 的确厉害,完全可以 hold 住这三个“最”字,实至名归。
什么是预处理器
预处理器是在程序源文件被编译之前根据预处理指令对程序源文件进行处理的程序。说白了,预处理器只不过是一个文本替换工具而已。CSS 预处理器则是通过将有特殊语法和指令的源代码处理成浏览器可使用的 CSS 文件的程序。
是 Sass 还是 SCSS?
SCSS 是 Sass 3 引入的新语法,语法上完全兼容原生 CSS,功能上完全继承 Sass,可以说是 CSS 和 Sass 的完美融合。SCSS 之于 Sass 犹如 CSS3 之于 CSS,ES6 之于 JS。所以别纠结,其实是一个东西啦。
接下来就细数 Sass 带给咱们的四大实用特性,想必你一定已经使用过它们中的一个或几个。
一、嵌套写法
想想之前咱们是怎样写原生 css 的:
.page .content .left-side .profile .name{
font-size: 2rem;
}
.page .content .left-side .profile .age{
color: red;
}
现在使用 scss 可以这样写:
.page{
.content{
.left-side{
.profile{
.name{
font-size: 2rem;
}
.age{
color: red;
}
}
}
}
}
编译后
.page .content .left-side .profile .name{font-size: 2rem;}
.page .content .left-side .profile .age{color: red;}
这种嵌套写法的好处是显然的:
- 结构清晰简洁,并且可与 html 文档结构对应起来;
- 减少了大量冗余重复的选择器编码;
二、属性值的复用——定义变量
变量一直是所有编程语言的标准配置。然而 CSS 就没有,再次证明 CSS 可能是一门假语言。好在 Sass 补上了这个短板。
没有变量之前的代码(这里以定义一系列表示成功风格的样式组件为例):
.success-bg{
background: #dff0d8;
}
.success-panel{
.panel-heading{
background: #dff0d8;
}
.panel-body{
border: 1px solid #dff0d8;
}
}
使用了变量后的代码:
$success-color: #dff0d8;
.success-bg{
background: $success-color;
}
.success-panel{
.panel-heading{
background: $success-color;
}
.panel-body{
border: 1px solid $success-color;
}
}
使用变量的好处是显而易见的:
- 方便了多人协同作战,将频繁使用的属性值定义成变量放在单独的文件里,各个开发人员可方便引用而不必再关注这些小细节,有语义的变量名使用起来也要比单调的CSS值容易的多;
- 极大地增强了代码的可维护性,便于局部和全局的样式风格统一控制;
三、文件级的复用——模块系统
模块化是软件工程的第一要务,是大型项目的必需建筑。软件工程的主要目标就是控制复杂度,这也正是模块化的目的。通过将一个大型复杂的工程拆解成一个个的小模块,使得校验、调试、测试都轻而易举。
CSS原生的 @import
提供了一个并没有卵用的假模块系统。Sass 对 @import
进行了拓展,实现了一个真正意义上甚至功能更强大的模块系统。Sass 选择对 @import
进行扩展,而不是新建一个指令,可见 import 这个关键字的语义之强,JavaScript 模块系统的关键字也是 import
。
没有模块系统之前:
<!-- index.html -->
<link rel="stylesheet" href="/your/site/common.css">
<link rel="stylesheet" href="/your/site/popup.css">
<link rel="stylesheet" href="/your/site/module_a.css">
<link rel="stylesheet" href="/your/site/site.css">
有了模块系统之后:
/* site.scss */
@import "common";
@import "popup";
@import "module_a";
<!-- index.html -->
<link rel="stylesheet" href="/your/site/site.css">
好处嘛自然不用多说了:
- 增删改模块以后是 css 自己家的事,别麻烦别人,不用再去动 html 了吼;-
- 模块系统使得项目并不会随着业务复杂度增加而变得更加复杂。增加功能时只需要横向扩展就行了,不会纵向延伸,从而能始终保证每个模块完整而简单;-
四、展示层的复用——混合指令
混合(mixin)特别类似于 JavaScript 中的函数,然而 Sass 提供了用于表达式计算的 @function
函数指令,这里就不好这么类比了。但其实就是这么个东西,调用的时候会返回一段样式。
比如下面一段存在重复样式的代码。
复用之前:
.description{
color: red;
border: 1px solid #e3e3e3;
border-radius: 2px;
}
.article{
color: #444;
border: 1px solid #e3e3e3;
border-radius: 2px;
}
稍作优化:
.description, .article{
border: 1px solid #e3e3e3;
border-radius: 2px;
}
.description{
color: red;
}
.article{
color: #444;
}
似乎不错,但是之后再新加类似样式时,
.description, .article, .style01, .style02{
border: 1px solid #e3e3e3;
border-radius: 2px;
}
.
.
.
.style01{}
.style02{}
每次都要改两个地方,很麻烦,很容易漏,尤其是将通用样式分离出来的话更容易出错。
再做优化:
.grey-border-radius{
border: 1px solid #e3e3e3;
border-radius: 2px;
}
.description{
color: red;
}
.article{
color: #444;
}
似乎好了一点,但这样的话,html 每个使用的标签都需要多加上一个 .grey-border-radius 类。很显然这是多余的。这种做法可以说是“凑合”。
使用 Sass 复用之后:
@mixin grey-border-radius{
border: 1px solid #e3e3e3;
border-radius: 2px;
}
.description{
@include grey-border-radius;
color: red;
}
.article{
@include grey-border-radius;
color: #444;
}
编译后的 css 输出:
.description {
border: 1px solid #e3e3e3;
border-radius: 2px;
color: red;
}
.article {
border: 1px solid #e3e3e3;
border-radius: 2px;
color: #444;
}
看到了吧,这种做法简直“完美”:
- 抽离公共的样式片段,便于多处复用;
- 将公共的样式片段放在单独的文件里,便于项目的多个文件复用;
- 对 html 的使用没有任何要求,css 自己家的事自己关起门来解决,绝不麻烦别人;
其他不常用且慎用的强大特性
如果熟练合理地运用上面的四大特性,你已经是CSS代码工程化方面的砖家了,所写出来的代码必是清晰易维护的。Sass 提供了更多的功能,但对普通开发者来讲,上面的四点只要使用熟练,已经完全够用了,其他的可看可不看。下面提供的功能希望大家慎用,有的是出于性能考虑,有的则是从开发维护的角度考虑。尤其不要为了秀技术而去使用它们,过犹不及,事缓则圆,此为中庸之道。
# 语义层的复用——继承机制
继承是面向对象程序设计的三大特性之一,这也是为什么说它是语义层复用的原因。你可以说一个错误信息框继承了一个信息框,而不能说一个错误信息框继承了一个灰色圆角,虽然也是可以强行这么说,但难免有些别扭哈哈。
比如说下面定义一组信息框的样式,包括默认,成功和错误的样式。
使用继承之前:
.msg{
border: 1px solid #e3e3e3;
background: #dff0d8;
}
.msg-success{
color: #4cae4c;
}
.msg-error{
color: #d43f3a;
}
同样是上面说到的问题,编写 html 时每个使用的标签都需要多加上一个 .msg 类,很多余。
使用继承之后:
.msg{
border: 1px solid #e3e3e3;
background: #dff0d8;
}
.msg-success{
@extend .msg;
color: #4cae4c;
}
.msg-error{
@extend .msg;
color: #d43f3a;
}
编译后
.msg, .msg-success, .msg-error {
border: 1px solid #e3e3e3;
background: #dff0d8;
}
.msg-success {
color: #4cae4c;
}
.msg-error {
color: #d43f3a;
}
可以看出,上面的效果使用混合(mixin)也可以完成。但不同的是:继承拷贝的是选择器,而混合(mixin)拷贝的是样式片段。
使用混合(mixin)还是继承(extend)?
你肯定以为既然继承拷贝的是选择器,而混合拷贝的是大段的样式,那当然是优先选择继承了。然而恰恰相反,推荐做法是 尽可能使用混合(mixin),具体原因 戳这里。
# 用于复杂计算的函数
这个功能主要用于值的计算,和 JavaScript 中的函数类似。
比如移动端开发时可以封装成一个函数用于把 px 转成 rem。
$baseFontSize: 20;
@function px2rem($val) {
@return $val/$baseFontSize + rem;
}
.big-text{
font-size: px2rem(30);
}
编译后:
.big-text {
font-size: 1.5rem;
}
这样在拿到设计MM给的视觉稿之后就可以直接使用 px 进行测量使用了。
# 完善的控制流
控制流即程序语言中的 if/else
,for
,while
等控制语句。Sass 同样提供了指令实现:
@if
@for
@each
@while
它们通常配合 @function
指令使用,然而功能虽强,却不常用到。毕竟样式表的功用主要是描述页面样式,而不是提供更多控制。因此在这里不展开研究,感性趣的 戳这里。
小结
Sass 完美弥补了上面原生 CSS 暴露的几个短板,同时新语法 SCSS 使 CSS 开发者可以无缝过渡,是 CSS 预处理器中当之无愧的佼佼者。使用 Sass 容易编写出结构清晰,可复用,易维护的工程样式文件,这正是工程化的期望。这么好的东西,速速用起来。
本文主要参考了 Sass 中文网。