# 预处理器

尽管CSS已经很棒了,但是还是缺少很多设计师和开发者所需要的特性。不过,现在已经有人开发了工具来填补这方面的空白,提高我们的开发效率。

这种类型的工具是CSS预处理器。我将会重新介绍什么是预处理器以及它能做什么,并使用它构建可扩展的,模块化的CSS。

# 什么是预处理器?

CSS预处理器可以在CSS中使用特殊的语法,然后编译成浏览器中运行的CSS。有些预处理器的语法尽可能地贴近CSS原生语法,而有些预处理器则偏向尽可能简化的语法。

我们来看个Stylus (opens new window)预处理的例子:

// 使用Stylus编码
@import 'vendor'

body
  font 12px Helvetica, Arial, sans-serif

a.button
  border-radius 5px

对于使用过Ruby (opens new window) 或是CoffeeScript (opens new window)的同学,可能会感到很熟悉,它去掉了花括号和分号,而使用空格来替代。空格缩进代表哪些属性适用于哪些规则,属性名称中不能有空格,因为第一个空格分隔了属性和属性值。

除了Stylus之外,还有两个预处理器也深受欢迎,Sass (opens new window)Compass (opens new window)), 以及Less (opens new window)

# 安装预处理器

对于安装预处理器,有些人(尤其是设计师,他们不那么熟悉命令行)会抱怨安装过于复杂。其实这取决于你的环境,如果环境允许,安装预处理器可以像把应用拖进你的应用文件夹中这么简单。

比如在Mac上安装Sass,我只要在终端上键入命令行即可

# 安装Sass
sudo gem install sass
# 安装Compass
sudo gem install compass

gem (opens new window)是Ruby的包管理工具,它已经在最新版本的Mac OS X中预装。

装好Sass之后,我可以设置Sass监控我的工作区

# 运行Sass
sass --watch before:after

before文件夹放置了所有的.scss(需要预编译的CSS文件)文件,after则是编译后的CSS文件存储的文件夹。任何在before文件夹里对.scss文件的修改都会自动编译成新的CSS文件,然后存储在after文件夹里。在开发过程中,这对于快速构建和测试十分方便。(.scss文件和普通.css文件一样,只不过使用Sass语法,我会在之后的章节里做介绍)

如果你不喜欢命令行,这里也有一个Compass Mac App (opens new window)

Less需要使用npm(node包管理工具 (opens new window))安装,所以你需要先安装node包管理工具,然后安装Less。Less有Javascript版本,可以在浏览器中运行。

<!-- Less运行在浏览器中 -->
<link rel="stylesheet/less" type="text/css" href="styles.less">
<script src="less.js"></script>

千万不要在实际站点中部署Less,将Less语法转换为CSS再部署。

# 更简单的Less命令行
lessc styles.less

网站开发越来越需要命令行工具了,它们可以精简你的开发流程,甚至能够让你脱离GUI工具,解决一些GUI工具并不能解决的事情。

# 预处理器中有用的特性

预处理器提供了很多有用的功能使得我们编写CSS更加简单。以下列出了其中一部分:

  • 变量(Variables)
  • 操作符(Operations)
  • 混入(Mixins)
  • 嵌套(Nesting)
  • 函数(Functions)
  • 插值(Interpolation)
  • 文件导入(File importing)
  • 扩展(Extending)

这些都什么意思呢?让我们对其中一部分做一个深入的了解。(我将会使用Sass来进行演示,不过Less和Stylus其实也有类似的概念和方法)

# 变量

任何写过CSS的人可能不只一次地希望可以在一个CSS文件中设置一个颜色值,然后在其他CSS文件中重用这个颜色。在Sass中,你可以用$定义一个变量,并对它进行赋值。

// 使用变量
$color: #369; 

body {
    color: $color;
}

.callout {
    border-color: $color;
}

编译器会将上述代码转换为以下代码

/* 编译后的变量 */
body {
    color: #369;
}

.callout {
    border-color: #369;
}

这对于网站整体配置来说十分有帮助。(值得注意的是,W3C正在制定CSS变量 (opens new window)规范的草案)

# 嵌套

编写CSS的时候,选择器链很常见。

/* 选择器链 */
.nav > li {
    float: left;
}

.nav > li > a {
    display: block;
}

嵌套可以使得这些样式能够在预编译CSS文件中有更加清晰的分组

.nav {
    > li { 
        float: left;
        > a {
            display: block;
        }
    }
}

每一组样式都嵌套在上面的样式中。

/* Sass生成的CSS */
.nav > li {
  float: left; }
  .nav > li > a {
    display: block; }

嵌套有助于理清样式和元素的关系,但是和自己缩进的样式并没有什么区别。不过,嵌套还可以减少你的输入,不用每次都输入.nav

# 混入(Mixins)

混入(Mixins)十分强大。混入是一组可以在CSS中重用的样式,并且可以传入参数,定制它的输出。混入最常见的一个功能是处理浏览器厂商的前缀。

// border-radius的混入示例
@mixin border-radius($size) {
  -webkit-border-radius: $size;
     -moz-border-radius: $size;
          border-radius: $size;
}

只要你定义了混入,那你就可以在CSS的任何地方通过include指令来调用它。

// border-radius的混入示例
.callout {
    @include border-radius(5px);
}

预处理器会将其编译成以下代码

/* 为border-radius混入生成CSS */
.callout {
  -webkit-border-radius: 5px;
     -moz-border-radius: 5px;
          border-radius: 5px;
}

# 函数

混入看起来已经像一个函数了。但是,Sass中的函数却能做更加强大的计算。例如,lighten函数允许传入一个颜色值和百分比,返回亮度调整后的颜色值。

// 使用函数调整颜色值
$btnColor: #036;
.btn { 
    background-color: $btnColor; 
}
.btn:hover { 
    background-color: lighten($btnColor, 20%); 
}

Sass自带很多这样方便的函数,Compass甚至提供了更多。(如果你打算使用Sass,我强烈推荐你使用Compass)

# 扩展

扩展能够让一个模块拥有另一个模块的属性。在Sass中,我们可以使用extend指令进行扩展。

// scss扩展
.btn { 
    display: block;
    padding: 5px 10px;
    background-color: #003366; 
}
.btn-default { 
    @extend .btn;
    background-color: #0066cc; 
}

Sass扩展会将btn中的样式应用与btn-default。Sass相当智能,它不会只是将规则简单地复制到btn-default,而是创建一个为第一组规则创建一个联合选择器。

/* 将Sass扩展语法编译为CSS */
.btn, .btn-default {
  display: block;
  padding: 5px 10px;
  background-color: #003366; }

.btn-default {
  background-color: #0066cc; }

扩展仅限于简单的选择器,例如,你不能扩展#main .btn。本章末尾,我们将会对扩展做进一步的讨论,分析它们是如何在SMACSS中进行应用的。

# 多说一句

这些只是预处理器的冰山一角,在它们的官网上有更多的特性和例子的介绍。起初可能会让你有些望而生畏,但是不要一开始就想着使用它们的所有特性。

# 陷入困境和摆脱困境

你可能听过这样一句话:“拥有权利的同时也被赋予了重大的责任”。这些预处理器提供了很多强大的功能,使得你的CSS预编译文件精简而优美。但是,一旦这些文件进行编译后,你的CSS文件可能变得臃肿并且难以调试。换句话说,你回到了原点😭。你的代码可能是手工写的,也可能是使用像Dreamweaver这样的所见即所得工具,甚至是通过命令行使用预编预器,但是不管你使用哪一种,代码都有可能会变得臃肿。当然,同样也可以写出出色的代码。

让我们来看下可能会在哪里遇到麻烦。

# 深嵌套

使用嵌套,你很容易会越嵌套越深,试想一下,如果你有如下代码:

// Sass深嵌套
#sidebar {
  width: 300px;
  .box {
    border-radius: 10px;
    background-color: #EEE;
    h2 {
      font-size: 14px;
      font-weight: bold;
    }
    ul {
      margin:0;
      padding:0;
      a {
        display:block;
      }
    }
  }
}

这样的代码并不常见。这是我在工作中发现其中的一个Sass文件里是这样写的。下面是它生成的代码:

/* 使用深层嵌套的Sass编译生成的CSS */
#sidebar {
  width: 300px; }
  #sidebar .box {
    border-radius: 10px;
    background-color: #EEE; }
    #sidebar .box h2 {
      font-size: 14px;
      font-weight: bold; }
    #sidebar .box ul {
      margin: 0;
      padding: 0; }
      #sidebar .box ul a {
        display: block; }

# SMACSS中的嵌套

SMACSS的适用性深度性质,决定了它需要最小化的深度嵌套。布局和模块的分离也避免了这些问题。如果使用嵌套,之前的例子可能会像下面一样:

/* SMACSS中的深度嵌套 */
#sidebar {
  width: 300px;
}

.box {
  border-radius: 10px;
  background-color: #EEE;
}

.box-header {
  font-size: 14px;
  font-weight: bold;
}

.box-body {
  margin: 0;
  padding: 0;
  a {
    display: block;
  }
}

几乎没有嵌套!!!这是因为我们没有必要用长长的选择器去获取样式。只有在需要定位模块中某个部分的元素时,我们才需要去考虑嵌套的问题。

而且对于浏览器来说,长长的选择器链远比样式规则确认更难处理。

# 不必要的扩展

让我们回到btn-default那个例子,再次看下之前的代码示例。

// 按钮Sass扩展
.btn { 
    display: block;
    padding: 5px 10px;
    background-color: #003366; 
}
.btn-default { 
    @extend .btn;
    background-color: #0066cc; 
}

这个方法让我们不再需要为元素指定btnbtn-default两个类名,只需要指定一个btn-default。这里我们将多余的声明从HTML中搬到了CSS中。

/* 类名在链接上的应用 */
<a class="btn-default">My button</a>

扩展出一个子模块是避免在HTML中定义多个类名的方法之一。这个过程中,命名规范会变得十分重要。如果一个模块名为btn,而子模块名为small,当我们将small应用于元素时,这样反而会更加难以理解,因为我们根本不知道small是什么含义,所以我们需要使用btn-smallbtn-small既可以将其作为子模块名,同时也可以让我们知道模块的用处。

而查看Sass源码,我们同样可以知道btn-default是一个子模块,因为我们使用了@extend。而查看生成之后的源码,我们也可以知道btn-default是一个子模块,因为很明显,它和btn类是一组的。

扩展不适用于跨模块。

// 跨模块Sass扩展
.box { 
    border-radius: 5px;
    box-shadow: 0 0 3px 0 #000000;
    background-color: #003366; 

}
.btn { 
    @extend .box;
    background-color: #0066cc; 
}

跨模块扩展将两个关注点绑定在一起。模块的样式将会有多个来源,不再是一对一。而像这样对CSS进行分组的后果就是你将会失去实时加载和按需编译的能力。你的_button_和_box_模块需要被同时加载。

甚至于在同一个模块中扩展也会导致项目的实时加载过于复杂。所以,在雅虎,页面加载时只会加载默认样式,而在次要模块请求时才会加载次级样式。这样会使得初始页面加载更加迅速。

# SMACSS扩展

SMACSS的扩展一般在HTML层进行处理而不是在CSS层,即通过在HTML中定义多个类名实现SMACSS的扩展。

<!-- 在一个按钮上定义SMACSS模块 -->
<a class="btn btn-default">My button</a>

这个方法的好处是可以辨别一个模块的根元素在哪里。因为当我们查看浏览器中编译好的HTML时,我们可能很难分辨模块从哪里开始,又从哪里结束。而有了根模块名,因其没有任何断字符,所以它们自然而然就变得易于查找了。

不过,把多个类加进HTML元素中时,看起来好像得了“多类症”。但是其实这些类是有必要的,它们区分了不同的模块并增强了元素的语义。

# 过度使用的混入

混入是避免代码重复的有效方式之一。但是,CSS类名也可以防止重复。

让我们来举个例子,多个模块共享一组样式:灰色背景色,蓝色圆角边框。你可能会创建如下混入器:

// Sass的混入用于通用样式
@mixin theme-border {
  border: 2px solid #039;
  border-radius: 10px;
  background-color: #EEE;
} 

.callout {
  @include theme-border;
}

.summary {
  @include theme-border;
}

上面代码将会被编译成

/* 混入编译后生成的CSS */
.callout {
  border: 2px solid #039;
  border-radius: 10px;
  background-color: #EEE; }

.summary {
  border: 2px solid #039;
  border-radius: 10px;
  background-color: #EEE; }

# SMACSS的重用模式

在上面那个例子中,因为重用部分是视觉处理效果,所以我们可以将其定义为一个单独的类。

/* 给通用模式定义一个类名 */
.theme-border {
  border: 2px solid #039;
  border-radius: 10px;
  background-color: #EEE;
} 

.callout {
}

.summary {
}

然后我们按需添加到元素中:

<!-- 应用类名 -->
<div class="callout theme-border"></div>
<div class="summary theme-border"></div>

# 参数化混入

参数化混入提供了CSS不能企及的功能,CSS必须创建无数个类名变体来达到参数化混入的效果。先前的章节的border-radius例子就是一个很好的参数化混入的例子。

# 预处理器SMACSS化(Smack that preprocessor)

我们已经讨论了预处理器的几个常见的缺陷,并将其和用SMACSS实现的方法做了比较。我们需要适度使用预处理器的这些特性,最好在使用之后检查预处理器生成的文件,看看最终的文件是否和你预期的一样。如果有很多重复代码,看看使用的方法是否需要重构。

接下来我们来看几个很棒的预处理器使用方法,这些方法能够帮助我们创建更加模块化的代码。

# 嵌套基于状态的媒体查询

前面(状态改变)章节我们已经知道,媒体查询是我们侦测和管理状态改变的途径之一。大多数教程可能都指导大家将所有的状态样式写进一个文件里,但是这使得一个模块的定义分散到了多个文件当中,让我们的维护更加困难。

在Sass中,媒体查询可以被嵌套,这能够反映状态的模块归属。

下面演示了媒体查询嵌套:

// Sass中的嵌套查询
.nav > li {
  width: 100%;

  @media screen and (min-width: 320px) {
      width: 100px;
      float: left;

  }

 @media screen and (min-width: 1200px) {
          width: 250px;
      }

}

上述例子,默认状态定义后,各个状态也被定义在该模块中,甚至,你可以在媒体查询中嵌套媒体查询,Sass会对各个媒体查询条件进行连接。

下面是嵌套例子生成的代码:

/* Sass媒体查询嵌套编译后的CSS */
.nav > li {
   width: 100%; }
@media screen and (min-width: 320px) {
  .nav > li {
    width: 100px;
    float: left; } }
  @media screen and (min-width: 1200px) {
    .nav > li {
      width: 250px; } }

编译过程中,Sass在选择器之外创建了媒体查询,并将选择器嵌套在里面。这个例子当中,我将小于320px的视图定义为默认状态,然后在达到320px时,布局变为了浮动布局,最后,我在1200px时改变了宽度,但不定义浮动布局。各个媒体查询都继承了默认状态的样式,这种方式我十分喜欢。

最棒的是,所有属于该模块的状态都被定义在该模块中。

# 组织好你的CSS文件

预处理器能够帮助我们对文件进行分离,这是SMACSS推荐的。

下面的策略能够帮助我们分离项目中的文件:

  • 将所有的基础规则形成单独的文件
  • 所有布局规则或是主要布局规则形成单独的文件
  • 每个模块形成单独的文件
  • 根据项目规模决定是否为子模块创建单独的文件
  • 全局规则形成单独的文件
  • 将模块的布局,状态以及影响到布局和状态的媒体查询放入到模块文件中

通过这种方式进行文件分离,我们的原型生成将会更加容易。我们可以为单个组件创建HTML模板,而且单个组件的CSS和模板可以进行隔离测试。

组件中特定的混入和变量应该被定义在自己的文件中。

一个文件结构样例

+-layout/
 | +-grid.scss
 | +-alternate.scss
 +-module/
 | +-callout.scss
 | +-bookmarks.scss
 | +-btn.scss
 | +-btn-compose.scss
 +-base.scss
 +-states.scss
 +-site-settings.scss
 +-mixins.scss

最后,创建一个包括其他文件主文件。对于很多网站来说,这可能只是意味着包含所有的文件在主样式表中。不过对于按需加载资源的项目来说,你可以只为特定屏幕引入必要的文件。

// 主文件中
@import 
   "site-settings", 
   "mixins",
   "base",
   "states",
   "layout/grid",
   "module/btn",
   "module/bookmarks",
   "module/callout";

预编译器会将主要文件编译成为开发和部署所用的单个文件。

当你进行项目部署,请为你的项目创建一个压缩版本的CSS文件。(你的开发环境中可能有部署文件的脚本,记得将预处理器集成到最终的构建过程中。)

# 使用Sass命令压缩CSS文件
sass -t compressed master.scss master.css

# 总结

我们已经了解到预处理器是什么以及如何去安装。我们也知道了预处理器一些流行的特性和它的一些缺陷。最后,我们也学习了如何使用预处理器来更简单地组织项目。很显然,预处理器是提升工作效率的有效工具。