배경
CSS 탄생 이후 기본 구문과 핵심 메커니즘은 본질적으로 변하지 않았습니다. 표현력이 거의 완전히 향상되었습니다. 처음에는 웹 페이지에서 CSS의 역할이 단지 보조적인 장식이었고 학습의 용이성이 가장 큰 요구 사항이었습니다. 그러나 이제는 웹 사이트의 복잡성이 달라졌으며 기본 CSS가 개발자를 압도했습니다.
언어의 기능이 부족하고 사용자의 운영 환경이 다른 옵션을 지원하지 않는 경우 해당 언어가 "컴파일 대상" 언어가 됩니다. 개발자는 개발할 다른 상위 수준 언어를 선택한 다음 실제로 실행하기 위해 하위 수준 언어로 컴파일합니다.
그래서 프론트엔드 분야에서는 하늘이 막중한 책임을 맡게 되었고, CSS 전처리기가 탄생하게 된 것입니다. 고대 언어 CSS는 다른 방식으로 웹 개발 요구 사항에 "재적응"되었습니다.
전처리기가 우리에게 주는 “초능력”
간단히 요약하자면 CSS 전처리기는 우리에게 몇 가지 중요한 기능을 제공했으며, 이를 얕은 것부터 깊은 것까지 나열하면 다음과 같습니다. (얼마나 많이 사용해도 상관없습니다.)
파일 분할
페이지가 점점 복잡해지고 CSS 파일도 로드해야 할 파일이 점점 커지고 있습니다. 대용량 파일을 분할해야 합니다. 그렇지 않으면 유지 관리가 어려워집니다. 기존 CSS 파일 분할 솔루션은 기본적으로 CSS 기본 @import 지시문이거나 HTML에서 여러 CSS 파일을 로드하는 것입니다. 이러한 솔루션은 일반적으로 성능 요구 사항을 충족할 수 없습니다.
CSS 전처리기는 @import 지시문의 기능을 확장하고 컴파일 프로세스를 통해 분할된 파일을 하나의 큰 파일로 다시 병합합니다. 이는 대용량 파일의 유지 관리가 불편한 문제를 해결하는 한편, 작은 파일 묶음을 로드할 때 발생하는 성능 문제도 해결합니다.
모듈화
파일 분할 개념을 한 단계 더 발전시킨 것이 바로 "모듈화"입니다. 큰 CSS 파일을 합리적으로 분할한 후 결과로 나오는 작은 파일 간의 관계는 트리 구조가 되어야 합니다.
트리의 루트 노드를 일반적으로 "엔트리 파일"이라고 하며, 트리의 다른 노드를 일반적으로 "모듈 파일"이라고 합니다. 항목 파일은 일반적으로 여러 모듈 파일에 의존하며 각 모듈 파일은 다른 터미널 모듈에 의존하여 전체 트리를 형성할 수도 있습니다.
모듈 메커니즘이 있는 다른 프로그래밍 언어를 사용해 본 적이 있다면 모듈화가 코드를 구성하는 아주 좋은 방법이자 개발자가 코드 구조를 설계하는 중요한 수단이라는 점을 이미 깊이 이해하고 있어야 합니다. 모듈은 코드 계층화, 재사용 및 종속성 관리를 명확하게 구현할 수 있으므로 CSS 개발 프로세스에서 현대 프로그램 개발의 편리함을 누릴 수 있습니다. 선택기 중첩선택기 중첩은 파일 내의 코드 구성 방법으로, 일련의 관련 규칙이 계층적 관계를 나타낼 수 있도록 합니다. 과거에는 이 목표를 달성하려면 다음과 같이만 작성할 수 있었습니다..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
变量
在变更出现之前,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
你可能已经意识到了,变量让开发者更容易实现网站视觉风格的统一,也让 “换肤” 这样的需求变得更加轻松易行。
运算
光有变量还是不够的,我们还需要有运算。如果说变量让值有了意义,那么运算则可以让值和值建立关联。有些属性的值其实跟其它属性的值是紧密相关的,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。
函数
把常用的运算操作抽象出来,我们就得到了函数。
开发者可以自定义函数,预处理器自己也内置了大量的函数。最常用的内置函数应该就是颜色的运算函数了吧!有了它们,我们甚至都不需要打开 Photoshop 来调色,就可以得到某个颜色的同色系变种了。
举个例子,我们要给一个按钮添加鼠标悬停效果,而最简单的悬停效果就是让按钮的颜色加深一些。我们写出的 CSS 代码可能是这样的:
.button { background-color: #ff4466; } .button:hover { background-color: #f57900; }
我相信即使是最资深的视觉设计师,也很难分清 #ff4466 和 #f57900 这两种颜色到底有什么关联。而如果我们的代码是用预处理语言来写的,那事情就直观多了:
.button $color = #ff9833 background-color: $color &:hover background-color: darken($color, 20%)
此外,预处理器的函数往往还支持默认参数、具名实参、arguments 对象等高级功能,内部还可以设置条件分支,可以满足复杂的逻辑需求。
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()
工程化
CSS 预处理语言无法直接运行于浏览器环境,这意味着我们编写的源码需要编译为 CSS 代码之后才能用于网页。这似乎是一个门槛,需要我们付出 “额外” 的成本。
但在目前的大环境下,大多数项目的前端开发流程已经包含了构建环节,比如选择任何一个脚本模块化方案都是需要在部署时走一道打包程序的。所以对大多数团队来说,这个门槛其实已经跨过去一大半了。
而一旦接受了这种设定,我们还可以享受到 “额外” 的福利。在给 CSS 的开发加入编译环节的同时,还可以顺道加入其它构建环节,比如代码校验、代码压缩、代码后处理等等。
“代码后处理” 是指 PostCSS 平台上各类插件所提供的功能,光是 Autoprefixer 这一项就已经值回票价了。我们再也不需要在 CSS 代码中手工添加浏览器前缀了,直接使用标准写法,剩下的事情让工具搞定吧!
更多浅谈 CSS 预处理器(一):为什么要使用预处理器相关文章请关注PHP中文网!