Sass带来的变革

发布于 大漠

接触Sass差不多有一个年头了,在这一年来的时间中,也花了不少心思在Sass的学习上。同时也让自己喜欢上了Sass,目前在自己的私人项目中,我一直都在使用Sass做为前端开发,用来处理CSS。其实,在W3cplus站点上,已经发布了近一百篇有关于Sass方面的教程(教程有自己的学习心得、有译文,也有其他同学的使用经验分享)。也自认自己是Sass在中国的推广者,其实我也更想做为Sass在中国的布道者,让更多的人了解他,学习他,使用他。

那么回到话题的正题中来,今天要说的是Sass带来的变革,其实标题有点浮夸,也可以当其是标题党吧。他是出自于自己在兄弟公司做分享的一份PPT。因为时常能碰到同学在问:

  • Sass是什么?
  • 怎么学习Sass?
  • 如何在项目中使用Sass?

等等一系列问题,其实在前面的教程中或多或少都有介绍,那么今天借此机会将前面所整理的教程、译文做一个系统的归纳吧,让有需要的同学能更好的学习和使用Sass。

##Sass是什么?

Sass是"Syntactically Awesome StyleSheets"的简称。那么他是什么?其实没有必要太过于纠结,只要知道他是“CSS预处理器”中的一种即可,简单点说,Sass就是“CSS预处理器”,你可以称其是工具或者是语言。如果你实在想知道他是什么?可以看Sass官网上的一段描述。

Sass is an extension of CSS that adds power and elegance to the basic language. It allows you to use variables, nested rules, mixins, inline imports, and more, all with a fully CSS-compatible syntax. Sass helps keep large stylesheets well-organized, and get small stylesheets up and running quickly, particularly with the help of the Compass style library.

##为什么选择Sass?

与Sass齐名的CSS预处理器还有LESS、Stylus等。这也引出一个激烈的讨论,Sass和LESS哪家技术强。而且这个讨论在很多论坛和平台都有出现过:比如2012年5月份Chris Coyier发表的《Sass vs LESS》一文,记得看文章后面的评论,因为评论的内容更精彩。

对于我个人为什么重新选择使用Sass,原因非常的简单,功能上来说他们都类似,也没有哪家比哪家强一说,但对于初学者,学习任何一门新技术,除了官网文档之外,希望有其他的教程或者资料,从这一点上来说,Sass相关的资料就非常的多。除此之外,使用Sass写的案例与框架也多。这就是我选择他的原因。如果你想先简单的了解Sass、LESS和Stylus有哪些不一样,再做出选择的话,可以看一篇2013年我在《程序员》杂志上发表的一篇文章《CSS预处理器——Sass、LESS和Stylus实践【未删减版】》。

####扩展阅读

##Sass功能与特性

Sass现在更新到3.4.7版本(写这篇文章时最新版本),其实Sass有很多功能和特性是CSS无法匹敌的,比如:变量、继承、混合宏、占位符、逻辑判断、函数、@at-root、列表和map等等。这些都是Sass的基本功能,可以说掌握了这些功能,配合你自己的创造力,可以使用Sass做更多有意义的事情。

####扩展阅读

##Sass学习路线

学习Sass还是具有一定的成本的。这样说吧,他和CSS基本类似,说得难听一点,你把你的.css更换成.scss文件,这就是现成的Sass。但要真正懂Sass还是需要一定的时间的。在这里,我来聊聊我是怎么样学习Sass的,或者说我学习Sass的一个路线是什么样的。

我将学习Sass分为三个阶段。

###初级阶段

初级阶段就是一个入门的过程,知道怎么使用Sass。在这个过程中主要包括以下几个部分:

  • 运行Sass环境
  • Sass安装
  • Sass语法
  • Sass编译
  • Sass调试

####运行Sass的环境

Sass是基于Ruby开发的,所以要运行Sass都需要一个Ruby环境。但并不是说你要懂得Ruby,你只需要在你的电脑中安装一个Ruby环境即可。如果你使用的是Mac电脑,那么就不需要安装,如果你使用的是Win系统,那么需要先在电脑中安装Ruby。也正是因为这个原因,很多同学觉得Sass要依赖于Ruby环境,而放弃使用Sass。

至于如何安装Ruby,就不做过多阐述,因为现在的应用软件安装都是非常简单的,一路下一步即可。

#####扩展阅读

####Sass安装

对于Sass安装来说是件非常简单的事情,只需要在你的命令终端输入一行命令即可:

gem install sass

提醒一下,在使用Mac的同学,可能需要在上面的命令加上sudo,才能正常安装:

sudo gem install sass

如果你是一位Sass发烧友,你也可以通过--pre参数来安装Sass开发版本,领略Sass的一些最新功能与特性:

gem install sass --pre

不过在天朝往往上面的命令让你无法正常实现安装,如果你碰到这样的事情,那么需要特殊去处理。可以到Rubygems网站上下载Sass安装包,然后在命令终端输入:

gem install <把下载的安装包拖到这里>

直接回车(Enter)即可安装成功。如果你不确认你的Sass是否安装成功,只需要输入命令:

sass -v

看到版本号就表示安装成功。

####Sass语法

Sass语法规则有两种,一种是通过tab键控制缩进的语法规则(缩进要求非常严格),这种语法对于熟悉Ruby的同学来说会非常的方便和喜欢。这种语法的Sass文件是以.sass后缀命名。另一种语法是SCSS,这是Sass的新的语法规则,他的外观和CSS的一模一样,文件后缀是.scss。如下所示:

//Sass语法
$width: 200px
.box
    width: $width

//SCSS语法
$width: 200px;
.box {
    width: $width;
}

来看个示意图:

Sass语法

正因为如此,有不少同学,使用的是Sass新的语法规则,而文件后缀依旧是.sass,这也就造成血案了,编译时说编译不出来。所以在此特别提醒新同学:.sass只能使用Sass老语法规则(缩进规则),.scss使用的是Sass新语法规则(类似CSS语法)。

上面只演示了最基础的语法规则,其实在定义混合宏,调用混合宏,他们都略有不同。对于前端人员,个人更建议使用SCSS语法风格,比较适应,也不会那么容易出错。

####Sass编译

众所周知,到目前为止,各浏览器是无法直接解析.scss或者.sass文件。换句话说,在Web实际掉用当中,还是需要调用.css文件。这个问题也困扰了很多初学者,常常有人会问,使用Sass进行开发,那么是不是直接通过<link>引用.scss.sass文件呢?那么这里告诉大家,在项目中还是引用.css文件,Sass只不过是做为一个预处理工具,提前帮你做事情,只有你需要的时候,他才能功效。

这样一来,在Sass开发之后,要使用写好的东西,让Web页面能调用,就得经过一个过程,这个过程就是Sass编译过程。Sass的编译有多种方法:

#####命令编译

如果你喜欢操纵你的命令终端,那么可以直接通过命令终端来对Sass进行编译,只需要命令终端输入:

sass <要编译的Sass文件路径>/style.scss:<要输出CSS文件路径>/style.css

这是对一个单文件进行编译,如果想对整个项目里所有Sass文件编译成CSS文件,可以这样操作:

sass sass/:css/

上面的命令表示将项目中sass目录中所有.scss(.sass)文件编译成.css文件,并且这些CSS文件都放在css目录当中。

在实际编译过程中,你会发现上面的命令,只能一次性编译。每次修改保存.scss文件之后,都得得新执行一次这样的命令,如此操作太麻烦,其实还有一种方法,就是在编译Sass时,开启watch功能,这样只要你的代码进行任何修改,他都能自动监测到代码的变化,并且给你直接编译过来。

sass --watch <要编译的Sass文件路径>/style.scss:<要输出CSS文件路径>/style.css

命令编译就是这么的简单。当然,使用sass命令编译时,可以带很多参数。

Sass编译命令参数

#####GUI编译

如果平时工作中不太喜欢使用命令终端的同学,可以考虑使用GUI界面工具来对Sass进行编译。当然不同的GUI工具操作方法略有不同。在此也不一一对编译的界面工具做详细的介绍。对于GUI界面编译工具,目前较为流行的主要有:

#####自动化配置编译Sass

喜欢自动化研究的同学,应该都知道GruntGulp这两个东东。如果您正在使用其中的任何一种,那么你也可以通过他们来配置,也可以完成Sass的编译。

//Grunt
module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        sass: {
            dist: {
                files: {
                    'style/style.css' : 'sass/style.scss'
                }
            }
        },
        watch: {
            css: {
                files: '**/*.scss',
                tasks: ['sass']
            }
        }
    });
    grunt.loadNpmTasks('grunt-contrib-sass');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.registerTask('default',['watch']);
}

//Gulp
var gulp = require('gulp');
var sass = require('gulp-sass');

gulp.task('sass', function () {
    gulp.src('./scss/*.scss')
        .pipe(sass())
        .pipe(gulp.dest('./css'));
});

gulp.task('watch', function() {
    gulp.watch('scss/*.scss', ['sass']);
});

gulp.task('default', ['sass','watch']);

#####扩展阅读

####Sass调试

Sass调试

Sass调试一直以来都是一件头痛的事情,使用Sass的同学都希望能在浏览器中直接调试Sass文件,能找到对应的行数。值得庆幸的是,现在要实现并不是一件难事了,只要你的浏览器支持"Sourcemap"功能即可。早一点的版本,需要在编译的时候添加--sourcemap参数:

sass --watch --scss --sourcemap style.scss:style.css

在3.3版本之上(我测试使用的版本是3.4.7),不需要添加这个参数也可以:

sass --watch style.scss:style.css

在命令终端,你将看到一个信息:

>>> Change detected to: style.scss
  write style.css
  write style.css.map

这时你就可以像前面展示的gif图一样,调试你的Sass代码。

#####扩展阅读

如果掌握了上面提到的知识,我想你已具备Sass的初级水平。你会安装Sass、知道Sass语法、会编写Sass代码,也能编译Sass,还能调试Sass代码。但这仅仅是Sass的基础知识。如果还要深入,还是需要花不少时间去学习与实战的。

###中级阶段

具有Sass初级阶段水平之后,你对Sass也有了基本的了解,也能用Sass去做一些简单的事情。如果要深入还是要继续往下学习的。接下来向大家简单介绍有关于Sass方面更有兴趣的东东。

####Sass的基本功能

Sass功能和特性有很多,要把所有东西介绍完,我想完全可以写一本书了,那么在这里主要向大家介绍一些Sass最基本、最常用的特性:

  • 变量
  • 混合宏@mixin
  • 继承
  • 占位符%placeholder
  • 嵌套
  • 运算符
  • 选择符&
  • 列表$list
  • 函数@function
  • map
  • 控制命令
  • @at-root

#####变量

先来看一张图

Sass变量

上图非常清楚告诉了大家,Sass的变量包括三个部分:

  • 声明变量的符号$
  • 变量名称
  • 赋予变量的值

定义变量,就可以在代码中调用了:

//SCSS
$color: orange !default;

.block {
  color: $color;
}

//CSS
.block {
  color: orange;
}

说到变量,大多数人都会想到全局变量和局部变量。早期的Sass是不具有这样的概念,但新版本中3.4之后有点这方面的意思了。来看一个简单的示例:

//SCSS
$color: orange !default;

.block {
  color: $color;
}

em {
  $color: red;
  
  a {
    color: $color;
  }
}

span {
  color: $color;
}
//CSS
.block {
  color: orange;
}

em a {
  color: red;
}

span {
  color: orange;
}

上面的示例演示可以得知,在元素内部定义的变量不会影响其他元素。如此可以简单的理解成,定义在元素外面的变量,比如$color:orange !default;是一个全局变量,而定义在元素内部的变量,比如$color:red;是一个局部变量。除此之外,Sass现在还提供一个!global参数:

//SCSS
$color: orange !global;

.block {
  color: $color;
}

em {
  $color: red;
  
  a {
    color: $color;
  }
}

span {
  color: $color;
}
//CSS
.block {
  color: orange;
}

em a {
  color: red;
}

span {
  color: orange;
}

!global从名称上看是一个全局变量,但和全面似乎没有太大区别,不过我们来换过一种测试效果,将!gobal放在内部,其也将会影响全局:

//SCSS
$color: orange !global;

.block {
  color: $color;
}

em {
  $color: red !global;
  
  a {
    $color: lime;
    color: $color;
  }
}

span {
  $color: yellow;
  color: $color;
}
.i {
  color: $color;
}
//CSS
.block {
  color: orange;
}

em a {
  color: lime;
}

span {
  color: yellow;
}

.i {
  color: red;
}

除非元素自身重置变量,才能覆盖!global的变量。

######扩展阅读

#####混合宏@mixin

Sass中的混合宏是通过@mixin来声明,然后通过@include来引用。混合宏主要功能就是将一些共用功能样式代码合并在一起。比如:

@mixin box-shadow($shadow...) {
  @if length($shadow) >= 1 {
    box-shadow:$shadow;
  } @else{
    $shadow:0 0 4px rgba(0,0,0,.3);
    box-shadow:$shadow;
  }
}

引用混合宏是通过@include:

.block {
  @include box-shadow;
}

.hidden {
  @include box-shadow(inset 0 0 1px rgba(red,.5),0 0 2px rgba(red,.25));
}

编译出来的CSS:

.block {
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
}

.hidden {
  box-shadow: inset 0 0 1px rgba(255, 0, 0, 0.5), 0 0 2px rgba(255, 0, 0, 0.25);
}

这样可能还看不出其特色,将上面的示例稍作变化:

//SCSS
.block {
  @include box-shadow;
}

.hidden {
  @include box-shadow(inset 0 0 1px rgba(red,.5),0 0 2px rgba(red,.25));
}

.header {
  @include box-shadow;
  
  h2 {
    @include box-shadow(1px 1px 2px rgba(green,.3),inset 0 0 2px rgba(green,.3));
  }
}
//CSS
.block {
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
}

.hidden {
  box-shadow: inset 0 0 1px rgba(255, 0, 0, 0.5), 0 0 2px rgba(255, 0, 0, 0.25);
}

.header {
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
}
.header h2 {
  box-shadow: 1px 1px 2px rgba(0, 128, 0, 0.3), inset 0 0 2px rgba(0, 128, 0, 0.3);
}

编译出来的CSS代码,大家不难发现,其中.block.header是样式代码是一样的,但Sass编译出来并没有将其合并在一起:

.block {
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
}

...

.header {
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
}

这其实也是Sass中混合宏@mixin最不尽人意之处,其最大特征就是可以为其传参数。那么大家需要记住一点,如果你的功用代码块,需要传参数,那么这个功能块应该使用Sass的混合宏@mixin来定义。

######扩展阅读

#####继承

继承对于CSS来说并不是陌生的事情,先来看张图:

Sass继承

图中代码显示.col-sub .block li,.col-extra .block li继承了.item-list ul li选择器的padding:0;ul li选择器中的list-style:none outside none;以及*选择器中的box-sizing:inherit;。在Sass中也具有继承一说,也是继承类中的样式代码块。在Sass中通过@extend来实现代码块的继承,如下所示:

//SCSS
.btn {
  border: 1px solid #ccc;
  padding: 6px 10px;
  font-size: 14px;
}

.btn-primary {
  background-color: #f36;
  color: #fff;
  @extend .btn;
}

.btn-second {
  background-clor: orange;
  color: #fff;
  @extend .btn;
}
//CSS
.btn, .btn-primary, .btn-second {
  border: 1px solid #ccc;
  padding: 6px 10px;
  font-size: 14px;
}

.btn-primary {
  background-color: #f36;
  color: #fff;
}

.btn-second {
  background-clor: orange;
  color: #fff;
}

从示例代码可以看出,在Sass中的继承也可以继承类中所有样式代码,而且编译出来的CSS会将选择器合并在一起,形成组合选择器:

.btn, .btn-primary, .btn-second {
  border: 1px solid #ccc;
  padding: 6px 10px;
  font-size: 14px;
}

#####占位符%placeholder

Sass中的占位符%placeholder功能是一个很强大,很实用的一个功能,这也是我非常喜欢的功能。他可以取代以前CSS中的基类造成的代码冗余的情形。因为%placeholder声明的代码,如果不被@extend调用的话,不会产生任何代码。来看一个演示:

%mt5 {
  margin-top: 5px;
}
%pt5{
  padding-top: 5px;
}

这段代码没有被@extend调用,他并没有产生任何代码块,只是静静的躺在你的某个SCSS文件中。只有通过@extend调用才会产生代码:

//SCSS
%mt5 {
  margin-top: 5px;
}
%pt5{
  padding-top: 5px;
}

.btn {
  @extend %mt5;
  @extend %pt5;
}

.block {
  @extend %mt5;
  
  span {
    @extend %pt5;
  }
}
//CSS
.btn, .block {
  margin-top: 5px;
}

.btn, .block span {
  padding-top: 5px;
}

从编译出来的CSS代码可以看出,通过@extend调用的占位符,编译出来的代码会将相同的代码合并在一起。这也是我们希望看到的效果,也让你的代码变得更为干净。

######扩展阅读

#####混合宏 VS 继承 VS 占位符

初学者都常常纠结于这个问题“什么时候用混合宏,什么时候用继承,什么时候使用占位符?”其实他们更有更的优点与缺点,先来看看他们使用效果:

//SCSS中混合宏使用
@mixin mt($var){
  margin-top: $var;  
}

.block {
  @include mt(5px);
  
  span {
    display:block;
    @include mt(5px);
  }
}

.header {
  color: orange;
  @include mt(5px);
  
  span{
    display:block;
    @include mt(5px);
  }
}
//CSS
.block {
  margin-top: 5px;
}
.block span {
  display: block;
  margin-top: 5px;
}

.header {
  color: orange;
  margin-top: 5px;
}
.header span {
  display: block;
  margin-top: 5px;
}

编译出来的CSS清晰告诉了大家,他不会自动合并相同的样式代码,如果在样式文件中调用同一个混合宏,会产生多个对应的样式代码,造成代码的冗余,这也是CSSer无法忍受的一件事情。不过他并不是一无事处,他可以传参数。个人建议:如果你的代码块中涉及到变量,建议使用混合宏来创建相同的代码块。

同样的,将上面代码中的混合宏,使用类名来表示,然后通过继承来调用:

//SCSS 继承的运用
.mt{
  margin-top: 5px;  
}

.block {
  @extend .mt;
  
  span {
    display:block;
    @extend .mt;
  }
}

.header {
  color: orange;
  @extend .mt;
  
  span{
    display:block;
    @extend .mt;
  }
}
//CSS
.mt, .block, .block span, .header, .header span {
  margin-top: 5px;
}

.block span {
  display: block;
}

.header {
  color: orange;
}
.header span {
  display: block;
}

使用继承后,编译出来的CSS会将使用继承的代码块合并到一起,通过组合选择器的方式向大家展现,比如.mt, .block, .block span, .header, .header span。这样编译出来的代码相对于混合宏来说要干净的多,也是CSSer期望看到。但是他不能传变量参数。个人建议:如果你的代码块不需要专任何变量参数,而且有一个基类已在文件中存在,那么建议使用Sass的继承。

最后来看占位符,将上面代码中的基类.mt换成Sass的占位符格式:

//SCSS中占位符的使用
%mt{
  margin-top: 5px;  
}

.block {
  @extend %mt;
  
  span {
    display:block;
    @extend %mt;
  }
}

.header {
  color: orange;
  @extend %mt;
  
  span{
    display:block;
    @extend %mt;
  }
}
//CSS
.block, .block span, .header, .header span {
  margin-top: 5px;
}

.block span {
  display: block;
}

.header {
  color: orange;
}
.header span {
  display: block;
}

编译出来的CSS代码和使用继承基本上是相同,只是不会在代码中生成占位符mt的选择器。那么占位符和继承的主要区别的,“占位符是独立定义,不调用的时候是不会在CSS中产生任何代码;继承是首先有一个基类存在,不管调用与不调用,基类的样式都将会出现在编译出来的CSS代码中。”

通过对比,总结一下:

- **混合宏@mixin:**如果相同代码块需要在不同的环境传递不同的值时,可以通过混合宏来定义重复使用的代码块,其不足之处就是编译出来的CSS代码什么多次出现调用的混合宏对应的代码块,使用文件变得臃肿,代码冗余。

  • **继承:**如果相同代码块不需要传递不同的值,并且此代码块已在Sass文件中定义,此进可以通过Sass的继承来调用已存在的基类。使用继承将会将调用相同基类的代码合并在一起。不足之处时,如果基类,并不存在于HTML结构时,不管调用与不调用,在编译出来的CSS中都将产生基类对应的样式代码。
  • **占位符%placeholder:**占位和继承基本类似,唯一不同的是,相同代码块并没有在基类中存中,而是额外声明。如果不调用已声明的占位符,将不会产生任何样式代码,如果在不同选择器调用占位符,那么编译出来的CSS代码将会把相同的代码合并在一起。

######扩展阅读

#####单类 VS 多类

在这种情形之下,引出一个新的争论点,那就是单类与多类的火拼。在实际项目中应该使用哪种方式。对于这样的争论一不是一时半刻的事情,其实他们一直都还在争论,而倒底应该使用单类还是多类,到目前也还没有结果。个人觉得还是根据实际情况出发吧。如果您对这方面的讨论和相关知识点感兴趣的话,不仿看看下面相关文章:

我们回过来头来细想,使用单类,在CSS中可能会造成代码的冗余,难于维护;而使用多类更易维护,而且代码更干净。但单类在HTML中更具语法义,也无需过多去维护HTML;如果使用多类,在HTML中语义化规划更具难度,对于结构也更难维护。其实将Sass结合在一起,将不会这么纠结。早前在写CSS的时候很多同学喜欢这样写:

//CSS
.paxs{padding:5px;}
.pas {padding: 10px;}
.pam{padding:15px;}
.pal{padding:20px;}
.paxl{padding: 25px;}
.paxxl{padding: 30px;}

.maxs{margin:5px;}
.mas {margin: 10px;}
.mam{margin:15px;}
.mal{margin:20px;}
.maxl{margin: 25px;}
.maxxl{margin: 30px;}

你的项目中可能会有一个基类文件,比如common.css,里面放了很多类似于上面的代码,然后在结构中通过多类引用:

//HTML
<div class="header maxs paxs"></div>

虽然这种方式解决了你的需求,但如果你的marginpadding值修改后,你需要同时修改你的HTML代码,或者在CSS中通过覆盖的方式来完成。但在Sass中,我们可以这样使用:

//SCSS中定义%placeholder,可以将这一部分代码放在_help.scss中
%paxs{padding:5px;}
%pas {padding: 10px;}
%pam{padding:15px;}
%pal{padding:20px;}
%paxl{padding: 25px;}
%paxxl{padding: 30px;}

%maxs{margin:5px;}
%mas {margin: 10px;}
%mam{margin:15px;}
%mal{margin:20px;}
%maxl{margin: 25px;}
%maxxl{margin: 30px;}

//实际中引用
.header {
    @extend %paxs;
    @extend %maxs;
}

//CSS
.header {
    padding:5px;
    margin:5px;
}

在HTML中,只需要引用单类:

//HTML
<div class="header"></div>

这种情形之下,就算你要修改marginpadding值,你也只需要通过修改.scss文件,无需修改任何HTML文件中代码即可。

相比之下,在实际中如何使用,或者你将选择单类还是多类方式,可以权衡其利弊。

#####嵌套

Sass中还提供了选择器嵌套功能,但这也并不意味着你在Sass中的嵌套是无节制的,因为你嵌套的层级越深,编译出来的CSS代码,选择器层级将越深,这往往是大家不愿意看到的一点。比如:

//SCSS
.block {
  color: green;
  
  span {
    color: blue;
    
    a {
      color: orange;
      
      i{
        color: lime;
        
        em &{
          color: red;
        }
      }
    }
  }
}
//CSS
.block {
  color: green;
}
.block span {
  color: blue;
}
.block span a {
  color: orange;
}
.block span a i {
  color: lime;
}
em .block span a i {
  color: red;
}

如此一来,在编写Sass代码时,使用选择器嵌套还是需要遵循一定的原则,其中最关键之处:别让你的嵌套层级超过四层。

 //SCSS
 .block {
  color: green;
  
  span {
    color: blue;
  }
  
  a {
    color: orange;
  }
    
  i{
    color: lime;
        
    em &{
      color: red;
    }
  }
}
//CSS
.block {
  color: green;
}
.block span {
  color: blue;
}
.block a {
  color: orange;
}
.block i {
  color: lime;
}
em .block i {
  color: red;
}

######扩展阅读

#####运算符

Sass还提供了一些运算符,可以在代码中做一些简单的计算,其中包括+-*/等。

//SCSS
$hello: hello;
$world: world;
$width: 200px;

.string {
  sting: $hello + $world;
}
body {
  width: $width * 2;
}
//CSS
.string {
  sting: helloworld;
}

body {
  width: 400px;
}

使用Sass运算符做一些运算时,在运算符前后要留有空格 。其中特别要注意的是font属性中font-sizeline-height简写时,其中/分隔线与运算符中的除号/相同,不管你是这样写:

//SCSS
$font-size: 2em;
$line-height: 1.5;
$font-family: "Arial";
body {
  font: $font-size / $line-height $font-family;
}
//CSS
body {
  font: 1.3333333333em "Arial";
}

还是这样写:

//SCSS
$font-size: 2em;
$line-height: 1.5;
$font-family: "Arial";
body {
  font: $font-size/$line-height $font-family;
}
//CSS
body {
  font: 1.33333em "Arial";
}

都将出错。此时你需要使用Sass中的插值#{}:

//SCSS
$font-size: 2em;
$line-height: 1.5;
$font-family: "Arial";
body {
  font: $font-size/#{$line-height} $font-family;
}
//CSS
body {
  font: 2em/1.5 "Arial";
}

同样的道理,在CSS属性中碰到缩写属性带有/符号的,在编写Sass代码时都需使用插值#{},以免造成不必要的编译错误。

#####选择符&

连体符&在Sass中也是一个很有意思,一个奇特的东东。特别是在选择器的嵌套,BEM+Sass的运用中,其发挥着与众不同的功能。看代码中的演示,将会来得实际一些:

//SCSS
.block {
  color: green;
  
  &:after {
    content:"";
    display:table;
  }
  #{&}__element{
    color:orange;
  }
  #{&}--modify{
    color:blue;
  }
  &.info {
    color: lime;
  }
  .page &{
    color: yellow;
  }
}
//CSS
.block {
  color: green;
}
.block:after {
  content: "";
  display: table;
}
.block .block__element {
  color: orange;
}
.block .block--modify {
  color: blue;
}
.block.info {
  color: lime;
}
.page .block {
  color: yellow;
}

&的奥妙尽在代码之中。其实你变换他的位置,简单点说吧,配合选择器嵌套,将会产生更多种不同组合效果,上面代码演示的是最常见的效果。如果您感兴趣,可以自己探索探索。

######扩展阅读

#####列表$list

Sass中的List是一个让人可爱又可恨的东西。主要是他的语法太宽松,你几乎可以做任何你想做的事情。如果要想更好的使用好Sass语言中的List功能,我们就必须的深入了解他。在这里无法详细的阐述Sass中的List是怎么一回事,我们来看点简单的。

在Sass中,声明List和声明变量非常的相似,而且其声明的方式方法也是多样,主要因为其语法非常宽松,如:

//定义变量
$list:();//定义一个空的列表
$list:(#b4d455,42,"awesome");
$list-space: "item-1" "item-2" "item-3";
//定义一个多维列表
$list: ( 
    ("item-1.1", "item-1.2", "item-1.3"), 
    ("item-2.1", "item-2.2", "item-2.3"),
    ("item-3.1", "item-3.2", "item-3.3")
);

在实际运用中,Sass的列表配合控制命令@each@for之类,或者函数nth()可以做很多事情,来看一个简单示例:

//SCSS
$list:(#b4d455,42,"awesome");
body {
  color: nth($list,1);  
  font-size: nth($list,2) * length($list) + px;
  font-family: nth($list,3);
}
//CSS
body {
  color: #b4d455;
  font-size: 126px;
  font-family: "awesome";
}

######扩展阅读

#####函数@function

在Sass中除了可以定义变量,具有@extend,%placeholders和Mixins等特性之外,还自备了一系列的函数功能。其主要包括字符串函数、数字函数、列表函数、Introspection函数以及三元函数等。

######字符串函数

######数字函数

######列表函数

######Introspection函数

######Miscellaneous函数

在这里把Miscellaneous函数称为三元条件函数,主要因为他和JavaScript中的三元判断非常的相似。他有两个值,当条件成立返回一种值,当条件不成立时返回另一种值:

if($condition,$if-true,$if-false)

上面表达式的意思是当$condition条件成立时,返回的值为$if-true,否则返回的是$if-false值。

除了这些函数之外,Sass还有颜色函数,以及后面新增的Maps函数选择器函数。有关于Sass所有自带函数的使用,还可以查看官网的函数列表

######自定义函数

很多时候,Sass自带的函数是无法满足业务的需求,这个时候,用户还可以根据自己的需求定义函数。如:将px转换成rem:

//SCSS
@function pxTorem($px,$browser-default-font-size){
    @return $px / $browser-default-font-size * 1rem;
}

h2 {
  font-size: pxTorem(32px,16px);
}
//CSS
h2 {
  font-size: 2rem;
}

######扩展阅读

#####map

Sass3.3新增了一个功能特性,那就是map。它可以帮助更好的组织Sass代码。从外形上看map长得有点类似于$list,其实你可以将其理解成类似其他语言中的数组,或JSON。在此就不做过多纠结,来简单看看他长得样子:

$map: (
  key: value,
  other-key: other-value
);

复杂一点的,可以map里面套map(记得前面的$list也可以是多层$lsit)。

// _config.scss
$breakpoints: (
  small: 767px,
  medium: 992px,
  large: 1200px
);

// _mixins.scss
@mixin respond-to($breakpoint) { 
  @if map-has-key($breakpoints, $breakpoint) {
    @media (min-width: #{map-get($breakpoints, $breakpoint)}) {
      @content;
    }
  }

  @else {
    @warn "Unfortunately, no value could be retrieved from `#{$breakpoint}`. "
        + "Please make sure it is defined in `$breakpoints` map.";
  }
}

// _component.scss
.element {
  color: hotpink;

  @include respond-to(small) {
    color: tomato;
  }
}

上面这个就是使用了map管理断点的一个示例,其编译出来的代码:

.element {
  color: hotpink;
}

@media (min-width: 767px) {
  .element {
    color: tomato;
  }
}

Sass中的map可以做的事情非常的多,特别借助Sass提供的map函数功能,能帮你做更多更有意义的事情,除此之外,你还可以开发一些适合自己业务需求的函数。

需要特别声明的是,Sass中的map有前面介绍的"Sourcemap"可不是同一个东东,千万别混淆了。

######扩展阅读

#####控制命令

Sass中控制命令指的是@if@each@for@while。具有一定的逻辑判断和循环遍历能力,这个对于懂JavaScript或者后端语言的同学来说一点都不难。对在CSS中是不可思议的一件事情,最起码到目前为止是不太可能的事情。但在Sass这样的CSS预处理器语言中实现了(当然,在LESS和Stylus中也具备这方面功能)。

######@if

@if是一个条件判断语句,简单点的就是如果条件成立,处理什么,反之条件不成立处理什么?在Sass中除了@if之外,还可以配合@else一起使用:

//SCSS
@mixin blockOrHidden($boolean:true) {
  @if $boolean {
    @debug "$boolean is #{$boolean}";
      display: block;
    }
    @else {
      @debug "$boolean is #{$boolean}";
      display: none;
    }
}

.block {
  @include blockOrHidden;
}

.hidden{
  @include blockOrHidden(false);
}
//CSS
.block {
  display: block;
}

.hidden {
  display: none;
}

######@each

@each是用来做遍历的,在Sass中$list中的值要一个一个输出,那么就可以使用@each命令来遍历输出。简单点,他们里面有一定的规律,让其按一定的规律输出。

//SCSS
$socials: twitter facebook twitter google rss email;

@mixin icon-socials {
  @each $social in $socials {
    .iocn-#{$social} {
      background: url("../images/#{$social}.png") no-repeat;
    }
  }
}

@include icon-socials;

//CSS
.iocn-twitter {
  background: url("../images/twitter.png") no-repeat;
}

.iocn-facebook {
  background: url("../images/facebook.png") no-repeat;
}

.iocn-twitter {
  background: url("../images/twitter.png") no-repeat;
}

.iocn-google {
  background: url("../images/google.png") no-repeat;
}

.iocn-rss {
  background: url("../images/rss.png") no-repeat;
}

.iocn-email {
  background: url("../images/email.png") no-repeat;
}

这个在Icon的运行中特别实用,上面演示的还是简单的一种,其实还可以更复杂一些:

//SCSS
$socials: twitter facebook twitter google rss email;

@mixin icon-socials {
  @each $social in $socials {
    .iocn-#{$social} {
      background: url("../images/icon.png") no-repeat 0  (-(index($socials,$social)) * 60px);
    }
  }
}

@include icon-socials;
//CSS
.iocn-twitter {
  background: url("../images/icon.png") no-repeat 0 -60px;
}

.iocn-facebook {
  background: url("../images/icon.png") no-repeat 0 -120px;
}

.iocn-twitter {
  background: url("../images/icon.png") no-repeat 0 -60px;
}

.iocn-google {
  background: url("../images/icon.png") no-repeat 0 -240px;
}

.iocn-rss {
  background: url("../images/icon.png") no-repeat 0 -300px;
}

.iocn-email {
  background: url("../images/icon.png") no-repeat 0 -360px;
}

######@for

Sass中的@for是一种循环遍历。他有两种方式:

@for $var from <start> through <end>

@for $var from <start> to <end>

其功能都是类似,只是截止的点不一同。来看一个对比示例:

//SCSS 
$grid-prefix: span !default;
$grid-width: 60px !default;
$grid-gutter: 20px !default;

%grid {
  float: left;
  margin-left: $grid-gutter / 2;
  margin-right: $grid-gutter / 2;
}
@for $i from 1 through 12 {
  .#{$grid-prefix}#{$i}{
    width: $grid-width * $i + $grid-gutter * ($i - 1);
    @extend %grid;
  }  
}

将上面的代码稍做修改,将@for through方式换成@for to:

//SCSS
@for $i from 1 to 13 {
  .#{$grid-prefix}#{$i}{
    width: $grid-width * $i + $grid-gutter * ($i - 1);
    @extend %grid;
  }  
}

这两种方式编译出来的结果:

.span1, .span2, .span3, .span4, .span5, .span6, .span7, .span8, .span9, .span10, .span11, .span12 {
  float: left;
  margin-left: 10px;
  margin-right: 10px;
}

.span1 {
  width: 60px;
}

.span2 {
  width: 140px;
}

.span3 {
  width: 220px;
}

.span4 {
  width: 300px;
}

.span5 {
  width: 380px;
}

.span6 {
  width: 460px;
}

.span7 {
  width: 540px;
}

.span8 {
  width: 620px;
}

.span9 {
  width: 700px;
}

.span10 {
  width: 780px;
}

.span11 {
  width: 860px;
}

.span12 {
  width: 940px;
}

这两段Sass代码并无太多差别,只是@for中的<end>取值不同。配合through<end>值是12,其遍历出来的终点值也是12,和<end>值一样。配合to<end>值是13,其遍历出来的终点值是12,就是<end>对就的值减去1

######@while

@while也可以做到遍历的效果,比如:

//SCSS
$types: 4;
$type-width: 20px;

@while $types > 0 {
    .while-#{$types} {
        width: $type-width + $types;
    }
    $types: $types - 1;
}
//CSS
.while-4 {
  width: 24px;
}

.while-3 {
  width: 23px;
}

.while-2 {
  width: 22px;
}

.while-1 {
  width: 21px;
}

在Sass中,@if@each@for@while这些控制命令配合@mixin@function可以实现一些复杂的功能。有兴趣的同学可以去尝试一下。

######扩展阅读

#####@at-root

@at-root从字面说就是在根上。其实@at-root早期是为BEM而生的,比如《Sass @at-root》一文中所介绍的。不过新的Sass对这个功能做出了调整。

在介绍选择器嵌套时,为了避免编译出来的CSS选择器不会因为Sass中嵌套过深而层级过多,在编写Sass代码时,尽量让嵌套层级不要超过三层。但往往使用嵌套都是为了更好的将代码按功能块来写。Sass考虑到能让开发员快速将编写的代码跳出层级限制,而又不使用编译出来的代码选择器冗余。将@at-root用于此处。来看一个示例:

//SCSS
.block {
  color: green;
  
  ul {
    list-style:none outside none;
    
    li {
      margin:0;
      
      @at-root a {
        color:green;
      }
    }
  }
}
//CSS
.block {
  color: green;
}
.block ul {
  list-style: none outside none;
}
.block ul li {
  margin: 0;
}
a {
  color: green;
}

编译出来的a直接跳出去了。但这也不是大家想要的,如果不想完全跳到最外面,那么@at-root又歇菜了,不过稍加改良一下,就OK。看两个@mixin:

//SCSS
@mixin parent-nth-status($index, $status, $place: suffix) {
  $func-name: parent-nth-status;
  $new-selectors: ();
  $selectors: &;

  $status: unquote($status);
  $place: unquote($place);

  @if not $selectors {
    @error "#{$func-name} should not at root!";
  }

  @if not $status or $status == "" {
    @error "#{$func-name} parameter $status error";
  }

  @each $selector in $selectors {
    $len: length($selector);
    $index: if($index < 0, $len + $index + 1, $index);
    $result: ();

    @for $i from 1 through $len {
      $item: nth($selector, $i);

      @if $i == $index {
        @if $place == suffix {
          $result: $item + $status;
        } @else if $place == prefix {
          $result: $status + $item;
        } @else if $place == prepend {
          $result: append($status, $item);
        } @else if $place == append {
          $result: append($item, $status);
        }
      }
      @else {
        $result: append($result, $item);
      }

    }

    $new-selectors: append($new-selectors, $result, comma);
  }

  @at-root #{$new-selectors} {
    @content;
  }
}
@mixin parent-status($status, $place: suffix) {
  @include parent-nth-status(-2, $status, $place) {
    @content;
  }
}
.tab {
  a {
    display: inline-block;
    padding: 10px 60px;
    cursor: pointer;
    &:hover {
      background: #AAA;
    }
    i {
      margin-left: 10px;
      @include parent-status(':hover') { color: red; }
      @include parent-status('.home-link') { background: blue; }
      @include parent-status('.about-link') { background: green; }
    }
  }
}

//CSS
.tab a {
  display: inline-block;
  padding: 10px 60px;
  cursor: pointer;
}
.tab a:hover {
  background: #AAA;
}
.tab a i {
  margin-left: 10px;
}
a:hover i {
  color: red;
}
a.home-link i {
  background: blue;
}
a.about-link i {
  background: green;
}

是不是让你觉得一下爽了。

##如何在项目中使用Sass?

很多时候,大家更为关心的如何在项目中使用Sass。其实这是一个很简单的问题,只是从未动手过而以。这里说说是怎么将Sass和项目结合在一起。

在平时做项目的时候,项目中都会包括几个常用的文件目录:

  • css:主要放置.css文件
  • js:主要放置.js文件
  • images:主要放置图片文件
  • html:主要放置.html.php之类文件(我一般喜欢直接放在项目根录下)

而Sass用到项目中并和上面无太多的差别,只需要在项目中创建一个sass目录,好放置.scss或者.sass文件。前面也说过了,最终在Web调用的文件是编译出来的.css文件。在没有一定经验的时候,可以将所有的.scss文件放在sass目录中。你也可以将其细分出来,因为很多代码不只用于一个项目,可以用到其他项目当中,我常常是这样组织我的.scss文件:

项目组织结构:

Sass项目组织结构

sass目录结构:

Sass项目组织结构

style.scss文件引用:

Sass项目组织结构

这是一个简单的项目,其实喜欢Sass的同学,可以看看其他项目的是如何组织相关文件,比如说Foundation、Bootstrap和Compass等。

######扩展阅读

##OOSass

OOSass其实就是将OOCSS和Sass两者结合在一起的产物。虽然OOCSS给我们写样式带来重大的变革,但依旧是痛楚不断。其实将OOCSS和Sass结合在一起,你将不再那么痛苦。在此,用一个简单的示例来演示整个过程。

下面有两很拙的按钮:

Sass项目组织结构

不能再拙的按钮了,咱也不纠结了。记得当初刚接触这个行业时,我傻傻这样做:

######HTML

<a href="#" class="twitter">Twitter</a>
<a href="#" class="facebook">Facebook</a>

######CSS

.twitter {
  border:3px solid #000;
  padding:10px 20px;
  color:#fff;
  border-radius:10px;
  background:red;
  text-decoration: none;
}
.facebook {
  border:3px solid #000;
  padding:10px 20px;
  color:#fff;
  border-radius:10px;
  background:blue;
  text-decoration: none;
}

突然发现,两个按钮长得差不多,只是背景色不同而以,后来知道相同的样式可以合并在一起,于是我知道这样做也可以,还比前面方便一点:

.twitter,.facebook{
  border:3px solid #000;
  padding:10px 20px;
  color:#fff;
  border-radius:10px;
  text-decoration: none;
}
.twitter {
  background:red;
}
.facebook {
  background:blue;
}

唯一变化,就是将.twitter.facebook两个选择器合并到一起,使用公用样式部分。但也麻烦,如果有一天,我新增了.google.rss等按钮呢?使用公用样式部分的选择器会变得非常的长,而且要不断的手工去添加。

随着看得多了,稍有点经验之后,我懂得给他们加一个公用的类名:

<a href="#" class=“btn btn-twitter”>Twitter</a>
<a href="#" class=“btn btn-facebook”>Facebook</a>

将公用的样式都写在.btn类名上面:

.btn{
  border:3px solid #000;
  padding:10px 20px;
  color:#fff;
  border-radius:10px;
  text-decoration: none;
}
.btn-twitter {
  background:red;
}
.btn-facebook {
  background:blue;
}

纳尼?这样也行。话说比前面方便些了,可又扯出另一个蛋疼的东西了,都得去动HTML。那么问题来了,有什么办法既不用动结构,又不用写那么多样式代码 ?其实这就是接下来要说的。这种情形Sass会变得更完美一些。

回到最初状态,我还是将结构写成:

<a href="#" class=“btn-twitter”>Twitter</a>
<a href="#" class=“btn-facebook”>Facebook</a>

在Sass中我先考虑到使用一个@mixin,将公用样式都赋予给这个@mixin

@mixin btn{
  border:3px solid #000;
  padding:10px 20px;
  color:#fff;
  border-radius:10px;
  text-decoration: none;
}

然后在实际中调用:

.btn-twitter {
  background:red;
  @include btn;
}
.btn-facebook {
  background:blue;
  @include btn;
}

编译出来的CSS:

.btn-twitter {
  background: red;
  border: 3px solid #000;
  padding: 10px 20px;
  color: #fff;
  border-radius: 10px;
  text-decoration: none;
}
.btn-facebook {
  background: blue;
  border: 3px solid #000;
  padding: 10px 20px;
  color: #fff;
  border-radius: 10px;
  text-decoration: none;
}

编译出来的代码跟初学CSS写出来的代码无两样。这对于一个有经验的CSSer来说,似乎无法接受。那就接着改良,咱们不使用@mixin了,而换成基类.btn,然后通过@extend来继承:

.btn{
  border:3px solid #000;
  padding:10px 20px;
  color:#fff;
  border-radius:10px;
  text-decoration: none;
}
.btn-twitter {
  background:red;
  @extend .btn;
}
.btn-facebook {
  background:blue;
  @extend .btn;
}

编译出来的代码:

.btn, .btn-twitter, .btn-facebook {
  border: 3px solid #000;
  padding: 10px 20px;
  color: #fff;
  border-radius: 10px;
  text-decoration: none;
}
.btn-twitter {
  background: red;
}
.btn-facebook {
  background: blue;
}

这个回到当初选择器合并状态了,纠结的是,我本来只需要用的选择器合并在一起,现在还多了一个基类的,真是画蛇添足。得继续改,这回咱使用占位符%

%btn{
  border:3px solid #000;
  padding:10px 20px;
  color:#fff;
  border-radius:10px;
  text-decoration: none;
}
.btn-twitter {
  background:red;
  @extend %btn;
}
.btn-facebook {
  background:blue;
  @extend %btn;
}

编译出来的CSS:

.btn-twitter, .btn-facebook {
  border: 3px solid #000;
  padding: 10px 20px;
  color: #fff;
  border-radius: 10px;
  text-decoration: none;
}
.btn-twitter {
  background: red;
}
.btn-facebook {
  background: blue;
}

这样舒服多了,就算你还要增加新的按钮,这种方式也就只需要调用了。而不需要再去考虑HTML。

其实除了上面的方法之外,还可以使用Sass的map功能:

//SCSS
$vars:(
  prefix-class: btn,
  twitter: red,
  facebook: blue
);
%btn{
  border:3px solid #000;
  padding:10px 20px;
  color:#fff;
  border-radius:10px;
  text-decoration: none;
}
.#{map-get($vars,prefix-class)}-twitter {
  background:map-get($vars,twitter);
  @extend %btn;
}
.#{map-get($vars,prefix-class)}-facebook {
  background:map-get($vars,facebook);
  @extend %btn;
}
//CSS
.btn-twitter, .btn-facebook {
  border: 3px solid #000;
  padding: 10px 20px;
  color: #fff;
  border-radius: 10px;
  text-decoration: none;
}
.btn-twitter {
  background: red;
}
.btn-facebook {
  background: blue;
}

其实实现上面的效果,在Sass中方法还有很多,大家应该通过实战或者经验,找出最适合自己的一种方案。

######扩展阅读

##编写Sass的技巧

编写Sass和CSS是类似的,初学者可能编写出来的Sass代码并不完美,甚至还会造成编译出来的CSS代码比自己使用CSS写出来的代码更垃圾。也很多同学因为这个原因而放弃了使用Sass这个高效的预处理器语言。其实不用担心,当你看得多、写得多、总结得多的时候,你的Sass代码会越来越优秀。总结几点我写Sass代码的体验:

  • 组织好Sass文件:这一步非常重要,因为其直接影响你如何@import文件,更直接会影响你编译出来的代码会不会重复;
  • 有效使用Sass变量:Sass变量虽强大,但并不是一味的将所有东西都定义为变量,一难维护,二也不一定全部使用上;
  • 减少对混合宏@mixin的依赖:@include进来@mixin,将会让你的代码变得更为冗余,保持一点,如果共用样式不涉及变量参数,坚决不使用混合宏来定义;
  • 拥抱%placholder:占位符%placeholder在Sass中是一个好东东,只要不需要变量的公用样式(基类样式)都可以将他们定义成%placholder;
  • 合理嵌套:保持你的嵌套不要超过三个层级,如果为了更好的编写或管理模块而使用选择器嵌套,也应该配合@at-root等相关功能,做到进退有度;
  • 使用@function做更多的事情:@function可以帮助你定义更多的功能,帮你做更多的事情

其实使用Sass,大家应该时刻记得:保持Sass的简单**。

######扩展阅读

##学习Sass的案例与资料

学习Sass其实最好的去处是Github。当然除了这个地方,你还可以在本站阅读Sass相关教程。下面列了几个国外关注度较高的Sass开源项目:

其实@结一同学也整理了几个好东东:

如果你对Sass的@mixin@functions感兴趣,可以观注我创建的一个库,里面整理了一些自己常用的@mixin@functions,而且还是一个非常有用的资源收藏夹,收录很多有关于Sass相关的教程与视频。

Sass Mixins & Functions

##总结

花了一周多的时间整理了这篇文章,也算是自己学习Sass之后的总结吧,也可以说是系统的对W3cplus站上发布的Sass教程的整理。希望这篇文章对大家学习Sass、了解Sass和使用Sass有所帮助。更希望的是,如果您有相关方面的经验,希望与我们一起分享。