新技术栈之-SASS全指南

没错,最近发文的频率提高了,主要是因为最近公司部门正在进行新技术栈的变革,从 backbone + marionette 的框架变身为 webpack + react + redux 的框架,其中拥有诸多变革,包括全版本 ES6 的推荐支持和 sass 等流行框架的运用。个人也在学习当中,学习的同时,就会把过程总结和发表出来,大家一起交流和学习。

今天带来的是 sass 的相关内容,是我对 sass 官方文档的理解和重点知识的整理。废话不多说,现在就开始吧。

sass基础

Sass 有两种语法。 第一种被称为 SCSS (Sassy CSS),是一个 CSS3 语法的扩充版本,这份参考资料使用的就是此语法。 也就是说,所有符合 CSS3 语法的样式表也都是具有相同语法意义的 SCSS 文件。 另外,SCSS 理解大多数 CSS hacks 以及浏览器专属语法,例如IE 古老的 filter 语法。 这种语种语法的样式表文件需要以 .scss 扩展名。

第二种比较老的语法成为缩排语法(或者就称为 “Sass”), 提供了一种更简洁的 CSS 书写方式。 它不使用花括号,而是通过缩排的方式来表达选择符的嵌套层级,而且也不使用分号,而是用换行符来分隔属性。 很多人认为这种格式比 SCSS 更容易阅读,书写也更快速。 缩排语法具有 Sass 的所有特色功能, 虽然有些语法上稍有差异; 这些差异在{file:INDENTED_SYNTAX.md 所排语法参考手册}中都有描述。 使用此种语法的样式表文件需要以 .sass 作为扩展名。

//文件后缀名为sass的语法
body
  background: #eee
  font-size:12px
p
  background: #0982c1

//文件后缀名为scss的语法  
body {
  background: #eee;
  font-size:12px;
}
p{
  background: #0982c1;
} 

上文的实例代码中,也已经看到,sass 可以通过类似于 js 的 // 双斜杆形式的单行注释,不过这种单行注释不会被转译出来(自然也不会生成到 css 中)。当然它还有一种是标准的css注释方式/* */,这种注释会生成到编译的 css 中。

在一个 sass 文件中导入另一个文件的时候,可以省略 sass 文件的后缀名,导入后,编译之后的 css 文件中包含被导入的 sass 文件内容。(css 文件的 import 不会追加到目标文件,而是在使用时去请求被 import 的文件),当然,在 sass 文件中 import css 文件也不会把 css 文件追加到 sass 中。

//a.scss
//-------------------------------
body {
  background: #eee;
}
被导入sass文件a.scss
@import "reset.css";
@import "a";
p{
  background: #0982c1;
} 
需要导入样式的sass文件b.scss
@import "reset.css";
body {
  background: #eee;
}
p{
  background: #0982c1;
}
转译出来的b.css样式

嵌套

sass 允许 css 的选择器和属性进行嵌套:

#main p {
  color: #00ff00;
  width: 97%;

  .redbox {
    background-color: #ff0000;
    color: #000000;
  }
}
sass 代码

编译后:

#main p {
  color: #00ff00;
  width: 97%; }
#main p .redbox {
  background-color: #ff0000;
  color: #000000; }
css 代码

这种嵌套可以避免很多重复书写的父级元素选择器,同时层级的嵌套也使 css 代码逻辑和层级关系更加清晰。

当然,有时候我们在嵌套中想使用外层父级元素,例如想给 a 标签添加一个 hover 的效果,这时候,我们可以用 & 符号来代替父元素:

a {
  font-weight: bold;
  text-decoration: none;
  &:hover { text-decoration: underline; }
  body.firefox & { font-weight: normal; }
}
编译前
a {
  font-weight: bold;
  text-decoration: none; }
  a:hover {
    text-decoration: underline; }
  body.firefox a {
    font-weight: normal; }
编译后

需要注意的是最后一行,当我们使用了 & 之后,就会无视外层父元素的层级,直接输出。

& 在编译时将被替换为父选择符,输出到 CSS 中。 也就是说,如果你有一个深层嵌套的规则,父选择符也会在 & 被替换之前被完整的解析

除了选择器可以嵌套之外,我们的属性也可以嵌套:

//sass style
//-------------------------------
.fakeshadow {
  border: {
    style: solid;
    left: {
      width: 4px;
      color: #888;
    }
    right: {
      width: 2px;
      color: #ccc;
    }
  }
}

//css style
//-------------------------------
.fakeshadow {
  border-style: solid;
  border-left-width: 4px;
  border-left-color: #888;
  border-right-width: 2px;
  border-right-color: #ccc; 
}

当然这也仅限于可以,可以看到属性嵌套编译后的代码似乎并不比上面的嵌套复杂多少。但是请注意,sass 的属性嵌套使得属性的逻辑更加清晰。(例如一个div的四个boder分别有不同的样式、宽度、颜色、圆角等)

在选择器的嵌套中,能不能再内部直接跳出到最外层呢?答案是肯定的,@at-root 可以用来跳出选择器嵌套。默认所有的嵌套,继承所有上级选择器,但有了这个就可以跳出所有上级选择器。

//sass style
//-------------------------------
//没有跳出
.parent-1 {
  color:#f00;
  .child {
    width:100px;
  }
}

//单个选择器跳出
.parent-2 {
  color:#f00;
  @at-root .child {
    width:200px;
  }
}

//多个选择器跳出
.parent-3 {
  background:#f00;
  @at-root {
    .child1 {
      width:300px;
    }
    .child2 {
      width:400px;
    }
  }
}

//css style
//-------------------------------
.parent-1 {
  color: #f00;
}
.parent-1 .child {
  width: 100px;
}

.parent-2 {
  color: #f00;
}
.child {
  width: 200px;
}

.parent-3 {
  background: #f00;
}
.child1 {
  width: 300px;
}
.child2 {
  width: 400px;
}

当然,@at-root 只会跳出选择器嵌套,而不能跳出@media@support,如果要跳出这两种,则需使用@at-root (without: media)@at-root (without: support)。这个语法的关键词有四个:all(表示所有),rule(表示常规css),media(表示media),support(表示support)。我们默认的@at-root其实就是@at-root (without:rule)。有兴趣自己查。

一般情况下,不建议使用跳出,这个东西会打乱代码组织,当然,使用跳出的话,能保证 sass 文件的层级与 html 层级对应,层次更加清晰,就看你怎么权衡了。

接下来要说的东西都属于 sass script 的范畴。

变量:$

sass 提供了类似于 js 的变量,提前定义,后面可以直接使用该变量书写 css 代码或参与运算、构建function等。我们来看一个简单的例子:

$width: 5em;
#main {
  width: $width;
}

需要注意的是,sass 的变量也会和 js 变量一个拥有作用域,在内部层级声明的变量,无法在该层级外部调用,但是可以在该层级更下级的层级中调用。如果你在sass根部声明了变量,那么这个变量在这个文件的任何一个位置都可以使用。

有一点需要说明,如果在写css 的时候,需要将变量的值以字符串直接输出,则需要使用:#{$variables} 例如:

//sass style
//-------------------------------
$borderDirection:       top ; 
$baseFontSize:          12px ;
$baseLineHeight:        1.5 ;

//应用于class和属性
.border-#{$borderDirection}{
  border-#{$borderDirection}:1px solid #ccc;
}
//应用于复杂的属性值
body{
    font:#{$baseFontSize}/#{$baseLineHeight};
}

//css style
//-------------------------------
.border-top{
  border-top:1px solid #ccc;
}
body {
  font: 12px/1.5;
}

我们在定义一个变量的时候,如果不确定之前是否已经定义过这个变量,则可以使用 !default 关键字,如果之前已经定义过这个变量,且该位置可用,则不会再次定义这个变量(或给这个变量赋值),需要注意的是:变量的值如果是 null 的话,会被 !default 当做没有值。

$content: "First content";
$content: "Second content?" !default;
$new_content: "First time reference" !default;

#main {
  content: $content;
  new-content: $new_content;
}
//编译后
#main {
  content: "First content";
  new-content: "First time reference"; }

SassScript 支持六种主要的数据类型:

  • 数字(例如 1.21310px
  • 文本字符串,无论是否有引号(例如 "foo"'bar'baz
  • 颜色(例如 blue#04a3f9rgba(255, 0, 0, 0.5)
  • 布尔值(例如 truefalse
  • 空值(例如 null
  • 值列表,用空格或逗号分隔(例如 1.5em 1em 0 2emHelvetica, Arial, sans-serif

需要重点说一下值列表,这里支持两种格式:

list

list数据可通过空格,逗号或小括号分隔多个值,可用nth($var,$index)取值。关于list数据操作还有很多其他函数如length($list)join($list1,$list2,[$separator])append($list,$value,[$separator])等,具体可参考sass Functions(搜索List Functions即可)

//一维数据
$px: 5px 10px 20px 30px;

//二维数据,相当于js中的二维数组
$px: 5px 10px, 20px 30px;
$px: (5px 10px) (20px 30px);
//sass style
//-------------------------------
$linkColor:         #08c #333 !default;//第一个值为默认值,第二个鼠标滑过值
a{
  color:nth($linkColor,1);

  &:hover{
    color:nth($linkColor,2);
  }
}

//css style
//-------------------------------
a{
  color:#08c;
}
a:hover{
  color:#333;
}

map

map数据以key和value成对出现,其中value又可以是list。格式为:$map: (key1: value1, key2: value2, key3: value3);。可通过map-get($map,$key)取值。关于map数据还有很多其他函数如map-merge($map1,$map2)map-keys($map)map-values($map)等,具体可参考sass Functions(搜索Map Functions即可)

//sass style
//-------------------------------
$headings: (h1: 2em, h2: 1.5em, h3: 1.2em);
@each $header, $size in $headings {
  #{$header} {
    font-size: $size;
  }
}

//css style
//-------------------------------
h1 {
  font-size: 2em; 
}
h2 {
  font-size: 1.5em; 
}
h3 {
  font-size: 1.2em; 
}

函数、运算和控制运算符

运算

所有数据类型都支持等式运算 (== and !=)。 另外,每种数据类型也有其支持的特殊运算符。

SassScript 支持数字的标准运算(加 +、减 -、乘 *、除 /和取模 %),并且,如果需要的话,也可以在不同单位间做转换:

p {
  width: 1in + 8pt;
}
p {
  width: 1.111in; }

数字也支持关系运算(<><=>=), 等式运算(==!=)被所有数据类型支持。

CSS 允许 / 出现在属性值里,作为分隔数字的一种方法。 既然 SassScript 是 CSS 属性语法的扩展, 他就必须支持这种语法,同时也允许 / 用在除法运算上。 也就是说,默认情况下,在 SassScript 里用 / 分隔的两个数字, 都会在 CSS 中原封不动的输出。

然而,在以下三种情况中,/ 会被解释为除法运算。 这就覆盖了绝大多数真正使用除法运算的情况。 这些情况是:

  1. 如果数值或它的任意部分是存储在一个变量中或是函数的返回值。
  2. 如果数值被圆括号包围。
  3. 如果数值是另一个数学表达式的一部分。
p {
  font: 10px/8px;             // 纯 CSS,不是除法运算
  $width: 1000px;
  width: $width/2;            // 使用了变量,是除法运算
  width: round(1.5)/2;        // 使用了函数,是除法运算
  height: (500px/2);          // 使用了圆括号,是除法运算
  margin-left: 5px + 8px/2px; // 使用了加(+)号,是除法运算
}

所有算数运算都支持颜色值, 并且是分段运算的。 也就是说,红、绿、蓝各颜色分量会单独进行运算。 例如:

p {
  color: #010203 + #040506;
  color: #010203 * 2;
}

+ 运算符除了可以计算数值(例如1+2, 3px+4px)之外,还可以用来连接字符串

p {
  cursor: e + -resize;
}
p {
  cursor: e-resize; }

注意,如果有引号的字符串被添加了一个没有引号的字符串 (也就是,带引号的字符串在 + 符号左侧), 结果会是一个有引号的字符串。 同样的,如果一个没有引号的字符串被添加了一个有引号的字符串 (没有引号的字符串在 + 符号左侧), 结果将是一个没有引号的字符串。 例如:

p:before {
  content: "Foo " + Bar;
  font-family: sans- + "serif";
}
p:before {
  content: "Foo Bar";
  font-family: sans-serif; }

在文本字符串中,#{} 形式的表达式可以被用来在字符串中添加动态值:

p:before {
  content: "I ate #{5 + 10} pies!";
}
p:before {
  content: "I ate 15 pies!"; }
// null 会被理解为空字符串
$value: null;
p:before {
  content: "I ate #{$value} pies!";
}
p:before {
  content: "I ate  pies!"; }

控制运算符

sass 允许我们使用 @if、@for、 @each、 @while 来控制流程。

//if 和 if else
p {
  @if 1 + 1 == 2 { border: 1px solid;  }
  @if 5 < 3      { border: 2px dotted; }
  @if null       { border: 3px double; }
}

$type: monster;
p {
  @if $type == ocean {
    color: blue;
  } @else if $type == matador {
    color: red;
  } @else if $type == monster {
    color: green;
  } @else {
    color: black;
  }
}

// for
@for $i from 1 through 3 {
  .item-#{$i} { width: 2em * $i; }
}

// each
@each $animal in puma, sea-slug, egret, salamander {
  .#{$animal}-icon {
    background-image: url('/images/#{$animal}.png');
  }
}

// while
$i: 6;
@while $i > 0 {
  .item-#{$i} { width: 2em * $i; }
  $i: $i - 2;
}

需要注意的是,在这些控制的逻辑里面,是不能 import 文件的,例如下方的代码是错误的,无法编译通过。

@if( true ){
  @import "a.sass"
}

函数

sass定义了很多函数可供使用,当然你也可以自己定义函数,以@fuction开始。sass的官方函数链接为:sass fuction,实际项目中我们使用最多的应该是颜色函数,而颜色函数中又以lighten减淡和darken加深为最,其调用方法为lighten($color,$amount)darken($color,$amount),它们的第一个参数都是颜色值,第二个参数都是百分比。

$grid-width: 40px;
$gutter-width: 10px;

@function grid-width($n) {
  @return $n * $grid-width + ($n - 1) * $gutter-width;
}

#sidebar { width: grid-width(5); }
body{
  font-size:$baseFontSize;
  color:lighten($gray,10%);
}
.test{
  font-size:pxToRem(16px);
  color:darken($gray,10%);
}

mixin 混合

sass中使用@mixin声明混合,可以传递参数,参数名以$符号开始,多个参数以逗号分开,也可以给参数设置默认值。声明的@mixin通过@include来调用。

//sass style
//-------------------------------
@mixin center-block {
    margin-left:auto;
    margin-right:auto;
}
.demo{
    @include center-block;
}

//css style
//-------------------------------
.demo{
    margin-left:auto;
    margin-right:auto;
}

mixin 调用时可直接传入值,如@include传入参数的个数小于@mixin定义参数的个数,则按照顺序表示,后面不足的使用默认值,如不足的没有默认值则报错。除此之外还可以选择性的传入参数,使用参数名与值同时传入。

//sass style
//-------------------------------   
@mixin horizontal-line($border:1px dashed #ccc, $padding:10px){
    border-bottom:$border;
    padding-top:$padding;
    padding-bottom:$padding;  
}
.imgtext-h li{
    @include horizontal-line(1px solid #ccc);
}
.imgtext-h--product li{
    @include horizontal-line($padding:15px);
}

//css style
//-------------------------------
.imgtext-h li {
    border-bottom: 1px solid #cccccc;
    padding-top: 10px;
    padding-bottom: 10px;
}
.imgtext-h--product li {
    border-bottom: 1px dashed #cccccc;
    padding-top: 15px;
    padding-bottom: 15px;
}

如果一个参数可以有多组值,如box-shadow、transition等,那么参数则需要在变量后加三个点表示,如$variables...

//box-shadow可以有多组值,所以在变量参数后面添加...
@mixin box-shadow($shadow...) {
  -webkit-box-shadow:$shadow;
  box-shadow:$shadow;
}
.box{
  border:1px solid #ccc;
  @include box-shadow(0 2px 2px rgba(0,0,0,.3),0 3px 3px rgba(0,0,0,.3),0 4px 4px rgba(0,0,0,.3));
}

@content

@content在sass3.2.0中引入,可以用来解决css3的@media等带来的问题。它可以使@mixin除了接受参数之外,还接受一整块样式,接受的样式用@content输出。

 //sass style
//-------------------------------                     
@mixin max-screen($res){
  @media only screen and ( max-width: $res )
  {
    @content;
  }
}

@include max-screen(480px) {
  body { color: red }
}

//css style
//-------------------------------
@media only screen and (max-width: 480px) {
  body { color: red }
}  

继承

好了,最后一个知识点,继承。

除了mixin 之外,我们还有另外一种方式来将通用代码块提取和重复使用,那就是继承。类似于 ES6 的继承,sass 在继承后,同样可以对继承而来的央视进行覆写和增加。

//sass style
//-------------------------------
h1{
  border: 4px solid #ff9aa9;
}
.speaker{
  @extend h1;
  border-width: 2px;
}

//css style
//-------------------------------
h1,.speaker{
  border: 4px solid #ff9aa9;
}
.speaker{
  border-width: 2px;
}

从sass 3.2.0以后就可以定义占位选择器%。这种选择器的优势在于:如果不调用则不会有任何多余的css文件,避免了以前在一些基础的文件中预定义了很多基础的样式,然后实际应用中不管是否使用了@extend去继承相应的样式,都会解析出来所有的样式。占位选择器以%标识定义,通过@extend调用。

//sass style
//-------------------------------
%ir{
  color: transparent;
  text-shadow: none;
  background-color: transparent;
  border: 0;
}
%clearfix{
  @if $lte7 {
    *zoom: 1;
  }
  &:before,
  &:after {
    content: "";
    display: table;
    font: 0/0 a;
  }
  &:after {
    clear: both;
  }
}
#header{
  h1{
    @extend %ir;
    width:300px;
  }
}
.ir{
  @extend %ir;
}

//css style
//-------------------------------
#header h1,
.ir{
  color: transparent;
  text-shadow: none;
  background-color: transparent;
  border: 0;
}
#header h1{
  width:300px;
}

如上代码,定义了两个占位选择器%ir%clearfix,其中%clearfix这个没有调用,所以解析出来的css样式也就没有clearfix部分。占位选择器的出现,使css文件更加简练可控,没有多余。所以可以用其定义一些基础的样式文件,然后根据需要调用产生相应的css。

ps:在@media中暂时不能@extend @media外的代码片段,即使生成到最根部也不行,以后将会可以。

好了,sass 的所有用法就到这里,感觉如何?是不是觉得对自己的css书写效率提升很多呢?快去试试吧。

另外,本文省略了很多不常用的内容,例如:@ 规则和指令、sass 选项、安装配置等,需要的同学,自行查阅官方api文档。