CSS 自诞生以来,基本语法和核心机制一直没有本质上的变化,它的发展几乎全是表现力层面上的提升。最开始 CSS 在网页中的作用只是辅助性的装饰,轻便易学是最大的需求;然而如今网站的复杂度已经不可同日而语,原生 CSS 已经让开发者力不从心。

当一门语言的能力不足而用户的运行环境又不支持其它选择的时候,这门语言就会沦为 “编译目标” 语言。开发者将选择另一门更高级的语言来进行开发,然后编译到底层语言以便实际运行。

于是,在前端领域,天降大任于斯人也,CSS 预处理器应运而生。而 CSS 这门古老的语言以另一种方式 “重新适应” 了网页开发的需求。

预处理器赋予我们的 “超能力”


简单梳理一下,CSS 预处理器为我们带来了几项重要的能力,由浅入深排列如下。(不用在意你用到了多少,无论深浅,都是获益。)

1.文件切分

页面越来越复杂,需要加载的 CSS 文件也越来越大,我们有必要把大文件切分开来,否则难以维护。传统的 CSS 文件切分方案基本上就是 CSS 原生的 @import 指令,或在 HTML 中加载多个 CSS 文件,这些方案通常不能满足性能要求。

CSS 预处理器扩展了 @import 指令的能力,通过编译环节将切分后的文件重新合并为一个大文件。这一方面解决了大文件不便维护的问题,另一方面也解决了一堆小文件在加载时的性能问题。

2.模块化

把文件切分的思路再向前推进一步,就是 “模块化”。一个大的 CSS 文件在合理切分之后,所产生的这些小文件的相互关系应该是一个树形结构。

树形的根结节一般称作 “入口文件”,树形的其它节点一般称作 “模块文件”。入口文件通常会依赖多个模块文件,各个模块文件也可能会依赖其它更末端的模块,从而构成整个树形。

以下是一个简单的示例:

entry.styl
├─ base.styl
│   ├─ normalize.styl
│   └─ reset.styl
├─ layout.styl
│   ├─ header.styl
│   │   └─ nav.syl
│   └─ footer.styl
├─ section-foo.styl
├─ section-bar.styl
└─ ...

(入口文件 entry.styl 在编译时会引入所需的模块,生成 entry.css,然后被页面引用。)

如果你用过其它拥有模块机制的编程语言,应该已经深有体会,模块化是一种非常好的代码组织方式,是开发者设计代码结构的重要手段。模块可以很清晰地实现代码的分层、复用和依赖管理,让 CSS 的开发过程也能享受到现代程序开发的便利。

3.选择符嵌套

选择符嵌套是文件内部的代码组织方式,它可以让一系列相关的规则呈现出层级关系。在以前,如果要达到这个目的,我们只能这样写:

.nav {margin: auto /* 水平居中 */; width: 1000px; color: #333;}
   .nav li {float: left /* 水平排列 */; width: 100px;}
       .nav li a {display: block; text-decoration: none;}

这种写法需要我们手工维护缩进关系,当上级选择符发生变化时,所有相关的下级选择符都要修改;此外,把每条规则写成一行也不易阅读,为单条声明写注释也很尴尬(只能插在声明之间了)。

在 CSS 预处理语言中,嵌套语法可以很容易地表达出规则之间的层级关系,为单条声明写注释也很清晰易读:

.nav
   margin: auto  // 水平居中
   width: 1000px
   color: #333
   li
       float: left  // 水平排列
       width: 100px
       a
           display: block
           text-decoration: none

4.变量

在变更出现之前,CSS 中的所有属性值都是 “幻数”。你不知道这个值是怎么来的、它的什么样的意义。有了变量之后,我们就可以给这些 “幻数” 起个名字了,便于记忆、阅读和理解。

接下来我们会发现,当某个特定的值在多处用到时,变量就是一种简单而有效的抽象方式,可以把这种重复消灭掉,让你的代码更加 DRY。

我们来比较一下以下两段代码:

/* 原生 CSS 代码 */
strong {
   color: #ff4466;
   font-weight: bold;
}

/* ... */

.notice {
   color: #ff4466;
}

// 用 Stylus 来写
$color-primary = #ff4466

strong
   color: $color-primary
   font-weight: bold

/* ... */

.notice
   color: $color-primary

你可能已经意识到了,变量让开发者更容易实现网站视觉风格的统一,也让 “换肤” 这样的需求变得更加轻松易行。

5.运算

光有变量还是不够的,我们还需要有运算。如果说变量让值有了意义,那么运算则可以让值和值建立关联。有些属性的值其实跟其它属性的值是紧密相关的,CSS 语法无法表达这层关系;而在预处理语言中,我们可以用变量和表达式来呈现这种关系。

举个例子,我们需要让一个容器最多只显示三行文字,在以前我们通常是这样写的:

.wrapper {
   overflow-y: hidden;
   line-height: 1.5;
   max-height: 4.5em;  /* = 1.5 x 3 */
}

大家可以发现,我们只能用注释来表达 max-height 的值是怎么来的,而且注释中 3这样的值也是幻数,还需要进一步解释。未来当行高或行数发生变化的时候,max-height 的值和注释中的算式也需要同步更新,维护起来很不方便。

接下来我们用预处理语言来改良一下:

.wrapper
   $max-lines = 3
   $line-height = 1.5

overflow-y: hidden
   line-height: $line-height
   max-height: unit($line-height * $max-lines, 'em')

乍一看,代码行数似乎变多了,但代码的意图却更加清楚了——不需要任何注释就把整件事情说清楚了。在后期维护时,只要修改那两个变量就可以了。

值得一提的是,这种写法还带来另一个好处。$line-height 这个变量可以是 .wrapper 自己定义的局部变量(比如上面那段代码),也可以从更上层的作用域获取:

$line-height = 1.5  // 全局统一行高

body
   line-height: $line-height

.wrapper
   $max-lines = 3

max-height: unit($line-height * $max-lines, 'em')
   overflow-y: hidden

这意味着 .wrapper 可以向祖先继承行高,而不需要为这个 “只显示三行” 的需求把自己的行高写死。有了运算,我们就有能力表达属性与属性之间的关联,它令我们的代码更加灵活、更加 DRY。

6.函数

把常用的运算操作抽象出来,我们就得到了函数。

开发者可以自定义函数,预处理器自己也内置了大量的函数。最常用的内置函数应该就是颜色的运算函数了吧!有了它们,我们甚至都不需要打开 Photoshop 来调色,就可以得到某个颜色的同色系变种了。

举个例子,我们要给一个按钮添加鼠标悬停效果,而最简单的悬停效果就是让按钮的颜色加深一些。我们写出的 CSS 代码可能是这样的:

.button {
    padding: 0px;"> #ff4466;
}
.button:hover {
    padding: 0px;"> #f57900;
}

我相信即使是最资深的视觉设计师,也很难分清 #ff4466 和 #f57900 这两种颜色到底有什么关联。而如果我们的代码是用预处理语言来写的,那事情就直观多了:

.button
   $color = #ff9833

padding: 0px;">   &:hover
        color: rgb(73, 73, 73); font-family: Arial, Helvetica, sans-serif; background-color: rgb(244, 237, 227);">此外,预处理器的函数往往还支持默认参数、具名实参、arguments 对象等高级功能,内部还可以设置条件分支,可以满足复杂的逻辑需求。

7.Mixin

Mixin 是 CSS 预处理器提供的又一项实用功能。Mixin 的形态和用法跟函数十分类似——先定义,然后在需要的地方调用,在调用时可以接受参数。它与函数的不同之处在于,函数用于产生一个值,而 Mixin 的作用是产生一段 CSS 代码。

Mixin 可以产生多条 CSS 规则,也可以只产生一些 CSS 声明。

一般来说,Mixin 可以把 CSS 文件中类似的代码块抽象出来,并给它一个直观的名字。比如 CSS 框架可以把一些常用的代码片断包装为 mixin 备用,在内部按需调用,或暴露给使用者在业务层调用。

举个例子,我们经常会用到 clearfix 来闭合浮动。在原生 CSS 中,如果要避免 clearfix 代码的重复,往往只能先定义好一个 .clearfix 类,然后在 HTML 中挂载到需要的元素身上:

/* 为 clearfix 定义一个类 */
.clearfix {...}
.clearfix::after {...}
<!-- 挂载到这两个元素身上 -->
<div class="info clearfix">...</div>
...
<footer class="clearfix">...</footer>

把表现层的实现暴露到了结构层,是不是很不爽?而在预处理器中,我们还可以选择另一种重用方式:

// 为 clearfix 定义一个 mixin
clearfix()
   ...
   &::after
       ...

// 在需要的元素身上调用
.info
   clearfix()

footer
   clearfix()

8.工程化

CSS 预处理语言无法直接运行于浏览器环境,这意味着我们编写的源码需要编译为 CSS 代码之后才能用于网页。这似乎是一个门槛,需要我们付出 “额外” 的成本。

但在目前的大环境下,大多数项目的前端开发流程已经包含了构建环节,比如选择任何一个脚本模块化方案都是需要在部署时走一道打包程序的。所以对大多数团队来说,这个门槛其实已经跨过去一大半了。

而一旦接受了这种设定,我们还可以享受到 “额外” 的福利。在给 CSS 的开发加入编译环节的同时,还可以顺道加入其它构建环节,比如代码校验、代码压缩、代码后处理等等。

“代码后处理” 是指 PostCSS 平台上各类插件所提供的功能,光是 Autoprefixer 这一项就已经值回票价了。我们再也不需要在 CSS 代码中手工添加浏览器前缀了,直接使用标准写法,剩下的事情让工具搞定吧!

浅谈 CSS 预处理器: 为什么要使用预处理器?的更多相关文章

  1. 转:浅谈CSS在前端优化中一些值得注意的关键点

    前端优化工作中要考虑的元素多种多样,而合理地使用CSS脚本可以在很大程度上优化页面的加载性能,以下我们就来浅谈CSS在前端优化中一些值得注意的关键点: 当谈到Web的“高性能”时,很多人想到的是页面加 ...

  2. 浅谈CSS模块化

    为什么要CSS模块化? 你是否为class命名而感到苦恼? 你是否有怕跟别人使用同样class名而感到担忧? 你是否因层级结构不清晰而感到烦躁? 你是否因代码难以复用而感到不爽? 你是否因为commo ...

  3. 浅谈css的预编译---less语言

    正如各位所知道的一样,css是一门标记性语言,语法相对简单,对使用者的要求也比较低 .不过可乐不知道友友们有没有发现,在使用css的时候需要书写大量看似没有逻辑的代码,不方便维护及扩展,不利于复用,尤 ...

  4. 浅谈css中的position

    什么是position,根据css 2.1中的描述,position和float的值决定了浏览器要采用那种定位算法来计算元素盒子的具体位置.先避开float不谈,本文主要介绍position属性的不同 ...

  5. 浅谈css中的盒模型(框模型)

    css中的盒模型是css的基础,盒模型的理解可以帮助我们进行对样式进行修改.废话不多说,进入正题: 在w3c中,CSS 框模型 (Box Model) 规定了元素框处理元素内容.内边框.边框 和 外边 ...

  6. 浅谈CSS盒子模型

    [摘要]盒子模型是CSS中的一个重要概念,虽然CSS中没有盒子这个单独的属性对象,但它却是CSS中无处不在的一个重要组成部分.掌握盒子模型的原理和使用方法可以极大地丰富HTML元素的表现效果,同时对于 ...

  7. 浅谈css的伪元素::after和::before

    css中的::after和::before已经被大量地使用在我们日常开发中了,使用他们可以使我们的文档结构更加简洁.但是很多人对::after和::before仍不是特别了解,究竟他们是做什么的?如何 ...

  8. 浅谈css的栅格布局

    栅格布局想必大家都很了解,我们做页面开发的时候,往往对页面板式的要求很高,如何对各个区域的内容排版,并使之对齐是我们的一大难题.而栅格系统就是我们排版的利器,他支持自动对齐.自动计算边距.流式布局等优 ...

  9. 浅谈CSS hack(浏览器兼容)

    今天简单写一点关于浏览器兼容的处理方法,虽然百度上已经有很多,但是我还是要写! 先看一个图 这个图描述了2016年1月至8月网民们所使用的浏览器市场份额(来源:http://tongji.baidu. ...

随机推荐

  1. POJ 2763 Housewife Wind 【树链剖分】+【线段树】

    <题目链接> 题目大意: 给定一棵无向树,这棵树的有边权,这棵树的边的序号完全由输入边的序号决定.给你一个人的起点,进行两次操作: 一:该人从起点走到指定点,问你这段路径的边权总和是多少. ...

  2. Docker 搭建 Tomcat + Mysql

    Docker 搭建 Tomcat + Mysql 准备 虚拟机 虚拟机安装Docker 在纯净的Centos镜像上搭建 Centos镜像准备 虚拟机上拉取 Centos 镜像: docker pull ...

  3. MySQL数据库基本用法-聚合-分组

    聚合 为了快速得到统计数据,提供了5个聚合函数 count(*)表示计算总行数,括号中写星与列名,结果是相同的 查询学生总数 select count(*) from students; max(列) ...

  4. docker部署springboot项目

    本文介绍一下docker如何部署springboot项目. 前提条件: 1.可以运行jar包的环境 2.机器上已经安装了docker 3.准备部署的springboot的jar包 4.Dockerfi ...

  5. 解决在Ue4中当摄像机拉得超级远时图像出现的撕裂情况

    当摄像机拉的超级远时场景就会出现这个情况.这个时候输入  r.SetNearClipPlane ,设置一下裁界面就好了

  6. BZOJ.4337.[BJOI2015]树的同构(树哈希)

    BZOJ 洛谷 \(Description\) 给定\(n\)棵无根树.对每棵树,输出与它同构的树的最小编号. \(n及每棵树的点数\leq 50\). \(Solution\) 对于一棵无根树,它的 ...

  7. BZOJ.1071.[SCOI2007]组队(思路)

    题目链接 三个限制: \(Ah-AminH+Bv-BminV\leq C\ \to\ Ah+Bv\leq C+AminH+BminV\) \(v\geq minV\) \(h\geq minH\) 记 ...

  8. Java并发编程(三)-- 多线程的“问题”

    竞态条件与临界区 在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源.当多个线程同时访问同一个资源,并且其中的一个或者多个线程对这个资源进行了写操作,才会产生竞态条件.多个线程 ...

  9. Python中的iteritems()和items()

    我用的是Python3.6 在Python3.x中,iteritems() 和 viewitems() 这两个方法都已经废除了,用 items()替换iteritems() ,for循环来遍历出来.

  10. Android应用程序开发疑问

    为什么android.util.log会提供五种不同级别的打印输出方式?(打印输出在Logcat窗口)比如:Log.v()用于打印比较琐碎的信息:Log.d()用于打印调试信息:Log.i()用于打印 ...