抛开语法,深度剖析CSS预处理器

背景

现在来说这些,显得晚了些,大家倾向于去关注最新、最酷的技术,所谓技术的潮流,处理器的概念不算新的,每个前端从业者,应该都使用过或者瞄过两眼。有些人,试用过之后觉得不错就一直用并且推广下去;有些在用了一下之后就放下了;而另外一些,可能看过之后就没有再用。

不管你是属于哪一类,其实,我们都应该是保持一种好奇和敬畏之心,去看待它们,毕竟,它们是应需而生,突破思维局限,打破语言局限,能够在一定程度上提高效率,制造便利的好工具。学习之,研究之,是有必要的。

先来看看目前为止CSS的痛点在哪里?

CSS的出现

1、为结构和表现分离提供了便利。
2、为一个文件多处引入,改一处多处改,提供了便利。

但是,其最大的缺陷在于,完全静态,所有代码都需要一板一眼的敲上去。比如:

1、嵌套
2、复用代码段
3、模块化开发的不灵活
4、不具备逻辑能力和抽象能力

或许,我们可以动用聪明的头脑去规划,去设计,来提高代码的可维护性、可读性和可复用性,但它的硬伤是难以突破的。
预处理器的共同点

大家都知道,处理器不止一种,比如sass、less、stylus等,很多人都问过这么个问题,我要选哪种好呢?它们有什么不一样吗?首先来看共同点:

1、变量

懂得编程的人都知道,变量,就是一个容器,你把一个值存进去,然后各处均可通过引用它去访问那个值。所以,它适合批量定义和修改。

就是这么简单,那么问题来了,什么时候适合创建一个变量?

  • 该值至少重复出现了两次;
  • 该值至少可能会被更新一次;
  • 该值所有的表现都与变量有关(非巧合)。

基本上,没有理由声明一个永远不需要更新或者只在单一地方使用的变量。一般来说,变量长这个样子的

不同的处理器前面的符号不同。

2、嵌套

来看一张大家都熟悉的代码段

明显可以看出,上面这段代码,在选择器部分,行与行之间都有一些重复,这样对于我们的书写带来很多重复。而处理器所提供的嵌套规则,使得我们免于不断的去写已经有了的父容器,可以直接在后面书写子容器的选择器,比如可以像这样:

当代码量足够多的时候,这样就相当提高效率。

3、模块化管理、易维护

当我们在做中、大型项目的时候,往往一个功能或者模块,会在很多页面都用到,如果我们把它们单独的放到所有页面对应的代码中,那么每次修改,我们都要去改所有的代码段,这显然是低效的,如果将它们放到一个大的公共文件中,有很可能造成冗余,或许,你会说,可以放在一个单独的文件中,然后引入到页面中,就行了,这样是可以,但是会多出来一个文件的请求,如果有多个,那么从维护性和性能两方面考虑的话,就得不偿失了。所以,我们可以使用处理器中的@import规则,把复用代码段提取,然后在需要的页面引入,这样,同样能达到“一处变、处处变”的效果,而且,不会有多余的请求发出,一个页面可以只有一个css文件即可。

比如这样:

我们可以把一个固定的组件,封装为一个单独的文件,然后在需要的文件里引入即可,这样方便维护,也不会在不需要的页面里放多余的代码段。

4、Mixins

在css中,有些时候,需要写一些不情愿,但又不得不去写的复杂代码段,最典型的例子就是CSS3的效果,为了兼容不同浏览器,加一堆浏览器前缀。如果使用的频次较高,这仍然会成为不小的成本。虽然我们可以用编辑器的插件去快速补全,那么也是需要重复操作的,何不让我们只写一次然后都复用之呢?

Mixins给我们提供的就是实现大段样式的重用,我们可以把需要写的东西事先封装起来,然后给其数值部分设置为参数,这样,我们在需要用到类似东西的时候,就可以直接调用已经定义好的mixin,对于尺寸大小的不同,只需要传入不同参数即可。

上面列出了一个参数的例子,我们知道,css属性里面很多时候不止一个值,这里也不局限只是一个属性,所以多个参数也是允许的,这样大家可能还会有个困惑,多个参数我是需要严格按照顺序书写吗?其实不必,sass给我们提供了 $name: value 的格式,比如我们可以这样定义:

只要对应的设置value就好。

5、extend扩展/继承

这可以说是处理器的一个亮点,你定义了一个类,后面如果有另一个类需要用到和已经定义了的类同样的属性和值,那么你可以通过@extend来直接引用已经定义的类,来再次使用它定义过的规则。

这样不会多出来很多重复代码段吗?可能你跟我有过同样的困惑,其实它生成的,是一个群组选择器,也就是多个类共用一段css样式规则,这样做的好处是,在你想定义有共性又有差异的一组元素时,不需要写多个类,只需要写它单独定义的类即可。

我个人认为,这样的写法,如果是在数量不多的时候,跟提取一个基类,再追加扩展类的成本是差不多的。并显示不出优势在哪里,只不过是多写了几个类在html里还是css里,那么如果是html的重复单元数量很多,差异项又不多的时候,使用这种方法是会更好一些,因为不需要有跟html的每一项进行绑定的基类了。亮点就在于你所定义的这个可以扩展的类,是不需要添加到html里面的,只是在编译之前的sass层面用来定义了一段公共块便于引用。比如:

此处“.error”这个类,就是sass层面所定义的复用类,不必使用在html中。

那么问题来了,我们是否有必要去定义一个从来不需要在html中使用的类?可能你觉得它的影响微乎其微,但从sass3.2之后,追求极致的开发者还是给出了对应的特性,那就是“%placeholder”,选择器占位符,有了它之后,我们就不需要定义一个没有实际作用的类了,当然,你可以选择去定义一个类并使用它,都随你~

6、运算

涉及到运算的,肯定就是数字相关,加减乘除,那么能进行怎样的运算呢?如下:

上图所示,可以用属性名直接计算,也可以使用数值,数值当中的单位需要统一,也可以缺省,甚至你可以写一个不存在的单位,编译的时候都不会报错,但如果前后不同,就会报错了。

应用场景:

猛一想,没有哪里需要用到,但是,我们仍然是落入了惯性思维的圈子,其实平时的很多计算我们都是心算或者计算器来算,然后写到样式里面了,比如说,当一个容器需要被几等分的时候,每一项需要多少;当一种布局需要一边定宽一边自适应时,自适应的这边应该是多宽。等等,这些都是可以使用运算来搞定的,维护起来也更容易些。

其实现在css3已经加入了”calc()”这个具备计算能力的函数,但是,在某些仍不支持它的浏览器中,预处理器的计算能够弥补这一不足。

7、函数

我们知道在很多编程语言里都有函数的概念,但早期的css是没有的,那么预处理器就给我们提供了一些函数,比如:

这里定义了两个变量和一个函数,函数有一个参数“$n”,通过编译,执行了函数之后,有了右边的css代码。
当然,这是个自定义的函数,预处理器有自己的一些内置函数,比如:

颜色函数:

rgb($red,$green,$blue):根据红、绿、蓝三个值创建一个颜色;

rgba($red,$green,$blue,$alpha):根据红、绿、蓝和透明度值创建一个颜色;

red($color):从一个颜色中获取其中红色值;

数字函数:

percentage($value):将一个不带单位的数转换成百分比值;

round($value):将数值四舍五入,转换成一个最接近的整数;

ceil($value):将大于自己的小数转换成下一位整数;

floor($value):将一个数去除他的小数部分;

以上列出的是sass中有的,是不是似曾相识?还有其他非常多的函数,多到什么程度呢,多到你怀疑它们是用来干嘛的~有兴趣,可以轻戳这里了解http://sass-lang.com/documentation/Sass/Script/Functions.html

是的,即使我鼓励大家用预处理器,但是不得不说,平时我们的项目中用不到这么多,所以,大家大概知道就好。

8、条件判断

提起条件判断,我们就会想到if…else…那么,css中有需要使用它的地方?刚开始我也是觉得没有,因为很多需要判断的地方,我们都交给js去做了,对吧?所以,还是有用武之地的。比如:

当浏览器版本低于IE9的时候,使用哪段样式;当我们某个元素需要切换状态的时候,使用哪段样式。等等。就像下面这样。

9、循环

循环的出现意味着存在本不可能出现在Sass中的复杂逻辑。在使用循环之前,务必确定这么做是有道理的,并且确认这么做可以解决问题。

我们会好奇的是,在哪里循环,用来干嘛?

说实话,一时能想到的用处真的不多,当我们需要用到它的时候,其实和js里面的循环用途类似,需要处理一组有着相同性质且有规律的东西,比如,一个列表的li,一个表格的tr、td,或者较有规律的雪碧图,再或者,你在自己能够驾驭的范围内自定义的一个属性或属性值列表。
比如像这样:

哇偶,酷毙了,是不是有些没想到还可以这样?其实从这里面就可以更明显的看出css和一些传统编程语言的差距在哪里,重复的事情,只做一次。

是什么让你还在犹豫?我猜你可能有以下几处困惑

1、变量的特殊化

这一点,是我本人,也是同事,都存在过的顾虑——我现在定了一个变量,全站都用,那如果我需要修改其中一处,或者产品经理非要改掉其中一处,怎么办?

曾经,我以为这个就是它的一个缺陷,后来才知道是浅尝辄止就否定它。真实情况是这样:

从图中可以看出,sass能够轻松搞定这种特殊化,这就是作用域的问题,全局变量和局部变量,和其他编程语言里的没什么不同~就是这么简单。

2、伪类、伪元素、容器外部嵌套,这些怎么办?

怎么办呢?,一个“&”就可以解决啦!下面看是如何实现的。


这是伪类和伪元素,那么容器的外部嵌套呢?这个相信你也有想过对么,当我写了一大串的嵌套之后,如果需要在外部加父容器进行限制的话,难道还要在外面套一层吗,显得太low了吧,其实,你可以这样:


只要把你想要套在外面的选择器写在前面,后面跟“&”就可以搞定了。

3、选择器层级过多

使用过预处理器的人,应该都有过这方面的担心,因为这个时候嵌套是很随意且轻松的,写一个最外面的父选择器,然后就一层层的往里套了,就有可能导致,生成的css代码出现层级过多的问题。其实就这个问题的话,个人以为,首先要从源头解决,什么是源头,就是你对自己代码结构的规划,html结构的合理精简,一般情况下,不会出现多于4层的容器嵌套,当然,如果你是从页面容器到最里面一层数的话,那超过的可能性就很大了,但觉得没多少人会这么去写吧?所以,按照模块化,组件化的思想去组织代码,就不会出现层级过多的问题。

4、生成冗余代码

这一点是我看到sass为我们提供了复用便利之后的直接反应,如果复用起来那么容易,我们会不会太依赖之,反而生成了很多没必要的冗余代码段呢?在这一点上,sass的确是没有提供方案的,从某种程度上来说,也不需要提供吧,这是一个需要我们自己去把控的东西。

mixin,何时使用mixin?

先来一段官方描述:如果你发现自己在不停地重复一段样式,那就应该把这段样式构造成优良的混合器,尤其是这段样式本身就是一个逻辑单元。再为其添加一个展示性的描述,能够清晰明了的看出它是用来干嘛的。

显然,你首先会想到的是需要各种浏览器厂商前缀的CSS3代码。一个好的事情是,mixin本身不会被编译到css代码中。
在实践过程中,我个人并不是完全赞同这种“重复样式就构造混合器”的做法,我们权当这是一种功能描述,在适合使用的时候使用。实际当中,个人更倾向于构建一个常用代码库,当需要使用的时候,为结构添加可用的类,这样既方便使用,也不会造成明显的性能问题,也不会多个地方不断在重复一个代码段。

适合使用mixin的地方在我看来是应该存在变化的,比如,我们不能保证每处的圆角都一样,每处的阴影都一样,每处的渐变都一样,当它们不一样时,很多时候只是数值改变,而性质未变,这就适合使用带参数的mixin来搞定了。

extend继承的工作细节

跟变量和混合器不同,继承不是仅仅用样式替换@extend处的代码那么简单,继承只会在生成CSS时复制选择器,而不会复制大段的CSS属性,其实这一点上面已经说过了,这里再提及一下。

5、@import性能问题?

如果你有这个疑问,那么又先入为主了哈。我们可以先来回顾一下CSS的“@import”会带来什么问题。

很多关于性能的建议,都说尽量避免使用@import,因为这样做会导致CSS无法并行加载,使用@import引用的文件只有在引用它的那个css文件被下载、解析之后,浏览器才会知道还有另外一个css需要下载,这时才去下载。

好的,原因知道了,有问题咱们就有对策是吧,我们在自己定义的所有样式规则之前引用不就可以了?而且再退一步说,谁告诉你sass编译完了之后还存在@import这么个东西的?,它只是在编译之前方便我们编写代码的一种方法而已,是不是觉得上当了?~所以,完全不用担心咯。

你可能忽略的一些点

1、属性嵌套

你很容易就会知道选择器之间的嵌套,但是,不仅是选择器,属性也是可以嵌套的哦,如下:

2、!default、!global

写css的时候,我们都会面临着一些维护问题,比如,类名的定义,冲突的避免,sass只是变着法的在写css,所以,问题是同样的,特别是在团队合作的时候。所以当你给定义变量的时候,给变量加上了!default,那么其他开发者定义了自己要用的变量之后,再引入了你的库,就不会担心二者有冲突,比如这样:

哪里不对劲呢,写在后面的变量权重反而更低?是的,!default的作用在这里就是降级让路,定义一个默认变量值,别的值都可以覆盖它。

而”!global”的作用恰恰相反,是用来升级覆盖,回到前面看过的一个例子再来看

本来局部变量只是在局部起作用,是不会改变全局的,但是这里加上了“!global”了之后,.sub-wrap-three的值变成了.sub-wrap-two里面定义的300px。

3、 嵌套导入

通过前面的内容,我们已经知道了,sass是可以导入我们封装起来的模块样式的,其实不止如此,还可以在定义的样式当中进行导入。比如这样:


4、list和map

其实上面那么多东西,就足以令任何一个页面仔头大了,可是sass仍不止这些,还有比较复杂的数据结构,比如:list和map。

list,就相当于js中的数组。用来定义一组可以访问和使用的值。比如:


map,是一种映射任何类型的键值对。跟什么差不多呢,你猜对了,json~比如:


5、警告和错误

什么鬼,还有完没完?这些又是用来干嘛的?

@debug; @warn; @error,就是这三位。
@debug,主要是用作调试 SassScript,这个不是我们关注的重点。
@warn,和我们通常的认知差不多,不会把程序给卡住无法执行,而是能够预测风险并提醒开发者。
@error,将会中断编译器的下一步进程。基本上,它们中断编译并像堆栈跟踪一样在输出流中显示相关信息,这对调试很有帮助。

说了这么多,该如何选择?

先来看看简史:

Sass 的第一次提交还要追溯到距今八年之久的 2006 年底。由Ruby语言编写。

Less 由 Alexis Sellier 于 2009 年创建。使用js编写。既可以在 客户端 上运行 (支持IE 6+, Webkit, Firefox),也可以借助Node.js或者Rhino在服务端运行。

stylus的第一次提交是在2010年11月。来自Node.js社区。

从这个时间线可以看出,sass最早,目前三者都有人使用。其中,sass和less都有官网的中文翻译。
如下:

sass http://www.sass.hk/ sass设计指南 http://sass-guidelin.es/zh/
less http://less.bootcss.com/
而stylus没有,只有张鑫旭早在2012年翻译的中文文档 http://www.zhangxinxu.com/jq/stylus/

他也写过一篇文章,文中说,他没有选用sass的原因是ruby http://www.zhangxinxu.com/wordpress/2012/06/stylus-nodejs-expressive-dynamic-robust-css/。
显然,从实际使用上来说,是不需要用到ruby的,故而,不应成为大家不用它的原因。

所以,其实你选用哪种都可以,就目前来看,sass是最强大,也是被很多人推荐使用的,bootstrap的新版也是抛弃less投奔sass而去,既然差别不大,何不选一个更好用的呢?~

##工具
预处理器已经是css的工具了,那么它还有工具吗?答案是肯定的,比如,sass的工具compass,less的工具est。这些工具,为预处理提供了很多封装好的东西可以直接拿来用,可以说,是帮你懒到家了哈。详情大家自己点击链接看哈,就不多说了。

##如果实在不感冒呢?
是的,你可能会觉得才华横溢的你用css完全能够胜任现在的工作,那么为什么还要去了解预处理器看起来那么复杂的语法(其实不复杂~),不妨可以使用它的部分特性,比如嵌套、mixin、模块化等等,总之,不需要排斥它,它可以给你带来一些便利,又何乐而不为呢?~