Sass 的数据类型
Sass 既然有了类似编程语言的功能,自然也就有了简单的数据类型。这里简单的介绍一些 Sass 中的数据类型,因为在后面的讨论中要用到有关的内容。
Sass 中主要有六种数据类型:
- 数字型 (e.g. 1.2, 13, 10px)
- 文本型,单引号、双引号或者不加都可以 (e.g. “foo”, ‘bar’, baz)
- 颜色值类型 (e.g. blue, #04a3f9, rgba(255, 0, 0, 0.5))
- 布尔值 (e.g. true, false)
- nulls (e.g. null)
- 值列表,通过空格或者逗号分割值 (e.g. 1.5em 1em 0 2em,Helvetica, Arial, sans-serif)
- 值映射,一个关键词对应一个值 (e.g. (key1: value1, key2: value2))
针对这些值都有不同的运算规则以及一些实用函数,最后两个主要用来传参等。后面会提到。
Sass 变量的作用域
变量是最基础也最常用的东西,例如一段 CSS 中有很多相同的颜色值(例如 #050709),传统的 CSS 中,你就需要不断的去复制这个值,而在 Sass 中,你就可以先在对应文件头部(一般会有一个 var.scss 文件专门用来放置变量等)声明一个变量 $co : #050709
然后在后面需要使用这个颜色值的时候,就可以直接写上 color: $co;
就可以了。
不仅仅需要输入的字符少了,而且根据规则起变量名让人更加易懂,此外后期需要修改这一种颜色的时候,只需要修改一下变量值即可全部修改,不需要搜索替换了。
但是这里我们要讨论的是 Sass 中变量的作用域问题,即变量放置的位置先后、同名变量之间的相互影响问题。
使用下面这段代码即可进行测试:
$width: 10px;
.wrap{
/*$width: 20px;*/
.main{
width: $width;
}
.sidebar{
/*$width: 30px;*/
width: $width;
}
/*$width: 20px;*/
}
.content{
width: $width;
}
/*$width: 20px;*/
进行编译之后输出(无视注释掉的东西):
.wrap {
/*$width: 20px;*/
/*$width: 20px;*/
}
.wrap .main {
width: 10px;
}
.wrap .sidebar {
/*$width: 30px;*/
width: 10px;
}
.content {
width: 10px;
}
/*$width: 20px;*/
现在是理所当然的常用模式,在最上面设置了变量为 10px,下面引用的地方全都变成了 10px。我们现在注释掉第一行的变量,然后解开最后一行的变量声明。
结果编译报错不通过,提示 Undefined variable
说明变量声明必须在调用变量语句的上面,这样才能正确的调用变量。
现在我们恢复到初始状态,开始测试变量是否具有类似块级作用域的特性。把上面所有注释取消掉:
$width: 10px;
.wrap{
$width: 20px;
.main{
width: $width;
}
.sidebar{
$width: 30px;
width: $width;
}
$width: 20px;
}
.content{
width: $width;
}
编译后得到:
.wrap .main {
width: 20px;
}
.wrap .sidebar {
width: 30px;
}
.content {
width: 20px;
}
这说明,Sass 中的变量没有块级作用域特性,而是随执行随覆盖随调用。调用的前面没有变量声明,就报错,有很多变量声明,就调用在它上面离它最近的变量值。
而每次做 Sass 和 LESS 对比的时候,总要提一下变量的作用域问题,因为 LESS 支持变量的块级作用域。对此我个人觉得,不要真把这类 CSS 预处理器当成编程语言来用,本来就是很简单的东西,没必要搞的太复杂,变量在最顶端声明一下,后面全局调用就可以了。
值得注意的是,如果一个项目太大,而且是很多人共同完成,这时候命名规划不好,变量容易被覆盖掉,这样就悲剧了。所以多人合作大项目,一定要制定好规范和命名等事情。
引用父级的 &
对于后代选择器这类的 CSS 代码,Sass 中只需要嵌套即可,这一点非常方便,省下了重复复制父级选择器,编写和阅读代码也变得十分轻松简单。
但有时候,我们也需要在嵌套结构中引用父级选择器,这时候就可以用 &
符号代替父级选择器,超级实用,例如:
.parent{
color: green;
.son{
color: red;
&:hover{
color: blue;
}
}
body.orange &{
color: orange;
}
}
将会生成:
.parent {
color: green;
}
.parent .son {
color: red;
}
.parent .son:hover {
color: blue;
}
body.orange .parent {
color: orange;
}
注意 &
与其他选择器的空格问题,例如上面的 :hover
。
让一切变成字符串 #{}
变量只能作为属性值来用,例如 color: $color;
,那变量能不能用在选择器或者属性上面吗?例如这样:
$name: foo;
$attr: border;
p.$name {
$attr-color: blue;
}
编译报错,这时候就需要 #{}
出场了,我们需要它来将变量值转换为字符串,这样就可以用在选择器和属性上了,例如:
$name: foo;
$attr: border;
p.#{$name} {
#{$attr}-color: blue;
}
这样就可以编译出来:
p.foo {
border-color: blue;
}
再来看下面这个例子:
p {
$font-size: 12px;
$line-height: 30px;
font: $font-size/$line-height;
}
目的很明确,想要声明变量然后用在 font 属性上,设置字体大小和行间距,结果编译后傻眼了,编译成了:
p {
font: 0.4;
}
斜杠被识别为除法运算,将两个变量进行了运算,并编译了执行结果。这样显然是不行的,不符合 font 属性缩写格式,这时候就需要 #{}
登场了,我们修改成:
p {
$font-size: 12px;
$line-height: 30px;
font: #{$font-size}/#{$line-height};
}
就可以正常输出了。
神奇的四则运算
Sass 支持 加法(+)、减法(-)、乘法(*)、除法(/)以及取余(%),这几种运算符号作用相信大家以及比较熟悉了,但是在 Sass 中还有一些需要注意的地方:
Sass 中的加法
加法的用法很简单,例如:
p {
$width: 10px;
$width2: 20px;
width: $width + $width2;
}
很明显的输出:
p {
width: 30px;
}
这里需要注意的是,在 Sass 数据类型中提到了,10px 这类数值虽然带有字符单位,但是也算是数值,Sass 会进行计算并对单位进行处理,至于计算是对单位的处理,后面再说。
加法运算不仅仅是计算数字,也可以跟 JavaScript 一样,连接字符串等。例如下面:
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 {
margin: 3px + 4px auto;
}
会编译成:
p {
margin: 7px auto; }
而不会是:
p {
margin: "3px4pxauto";
}
除此之外,如果你想在一段字符串中进行计算并输出,就需要用 #{}
了,例如:
$w : 10;
p:before {
content: "I ate #{5 + $w} pies!";
}
将会输出:
p:before {
content: "I ate 15 pies!";
}
否则将会原封不动的输出运算表达式。
Sass 中的除法
除法也有很多需要注意的地方,因为除法的运算符 /
在 CSS 中也有遇到,例如 font 缩写属性时候的 font-size 和 line-height 属性,就需要 /
来分割。所以,在这些包含 /
的 CSS 属性中对应位置的值,是不会参与运算的,除了下面情况下:
- 如果两个值其中一个或两个存放在变量中或者是由函数返回的值。
- 如果值被包裹在一对括号里面
- 如果值被作为另一个表达式的一部分
例如:
p {
font: 10px/8px; // 纯 CSS 不会运算
$width: 1000px;
width: $width/2; // 使用变量,执行运算
width: round(1.5)/2; // 使用函数返回值,执行运算
height: (500px/2); // 使用括号包裹,执行运算
margin-left: 5px + 8px/2px; // 用了加法,作为表达式的一部分,执行运算
}
编译后输出:
p {
font: 10px/8px;
width: 500px;
height: 250px;
margin-left: 9px; }
如果想要避免在使用变量的时候,进行了运算,在上面提到了那个例子,使用 #{}
来包裹变量,转换成字符串,这里不再赘述。
Sass 运算注意事项
1, 建议运算符之间空开一个空格。当你试着执行下面这句代码,你会发现编译器把 $h-
识别成了一个变量:
$w : 7px;
$h : 10px;
p:before {
width: $h-$w;
}
2, 注意运算单位。虽然 10px 也被看做是数值型,可以进行数值运算,但是对于单位是有要求的。单位也会参与运算。在运算中,两个操作数单位可以一致,也可以一个数没有单位,也可以都没有,而对于不同的运算操作符,单位运算结果也不同,一般来说测试结果如下:
- 加法:都没有单位输出纯数字;一方有单位,则结果输出该单位;两方相同单位,结果输出该单位;双方单位不同,报错。
- 减法:类似加法。
- 除法:两方相同单位,结果无单位;都没有单位,结果无单位;一方有单位另一方无单位,报错。
- 乘法:两方相同单位,报错;一方有单位,结果输出该单位;两方都无单位,输出无单位。
你可以用下面代码进行测试:
$w : 7;
$h : 10;
p:before {
width: $h * $w;
}
至于除法和乘法奇葩的运算规则,正如上面说的那样,单位也会参与运算。除法运算的时候,双方都有单位的时候,单位就被消掉了,得到一个数值。乘法的时候,双方都有单位,结果是单位的平方(px x px),自然也不行。
深入理解 @extend
@extend 这个 Sass 命令是非常实用的功能,简单的方法却极大的解决了 CSS 的代码复用问题。例如在所有的项目中都有了 .clearfix 清除浮动类,在应用这个类的时候,通常会在 HTML 需要的结构上添加这个类,但是这样做就会造成 CSS 和 HTML 的耦合,不利于后期维护。
如果只在 CSS 中去对每个结构实现 clearfix 的效果,要么把这段代码不停的复制到对应选择器中,产生大量冗余,要么就把这个结构选择器翻到上面去找 .clearfix 类,加在后面,费时费力。
而使用 Sass 之后,你只需要像下面这样:
.post{
@extend .clearfix;
...
}
就会自动的把 .post 加到 .clearfix 中实现 .post 结构清除浮动。当然,这只是 @extend 最基础的用法,下面讨论一下进阶用法。
@extend 可扩展的选择器
在了解 @extend 得基础功能之后,我扩展了一下类似 .icon .icon-button
这样的类,即 @extend .icon .icon-button;
这样写。结果报错,现在想想确实有问题,中间有空格分隔开了,就不背识别了。结果尝试加上双引号,也无解。
所以 @extend 中只能扩展单个选择器,例如:.icon-button
、a:hover
、a.user:hover
等。不过虽然每次 @extend 的是一个整体的选择器,但是你可以将多个选择器写进一条 @extend 命令中,用逗号分割,例如:@extend .message, .important;
,表示当前结构复用 .message
和 .important
的代码。
所以你可能需要想好并规划好 CSS 可复用模块,然后再在后面 @extend 。此外需要注意的是,@extend 命令无法用在其他 @ 命令中,例如用在 @media 命令中。当你在上面定义 .clearfix 类时,在 Media Queries 代码里面就无法扩展 .clearfix 类。但是你可以在 Media Queries
里面再定义一个 .clearfix 然后扩展一下。
理解 @extend 的扩展方法
来看一下下面代码的编译结果:
#admin .tabbar a {
font-weight: bold;
}
#admin .overview .fakelink {
@extend a;
}
编译结果:
#admin .tabbar a,
#admin .tabbar .overview .fakelink,
#admin .overview .tabbar .fakelink {
font-weight: bold; }
看着这一长串选择器,就比较晕了,为什么会组合出这样的一批选择器?慢慢的来分析一下就知道了。首先,@extend 命令的用处是用来将当前选择器与命令扩展的选择器共用一段 CSS 代码,所以要把当前选择器与命令扩展的选择器组合放在一起,并且要考虑到大部分情况,再来分析一下编译后的结果:
#admin .overview .fakelink
选择器要与 #admin .tabbar a
共用一段 CSS 代码,而且共用的是 a
标签的代码。由于 #admin
是相同的,所以统一放在最前面,只需要组合后面选择器即可。
既然扩展 a
标签,就先用 .overview .fakelink
替换掉 a
组合成了 #admin .tabbar .overview .fakelink
选择器,由于不知道 .tabbar
与 .overview
类结构的包裹情况,所以又颠倒位置生成了 #admin .overview .tabbar .fakelink
这样一个选择器。这样就足够了,因为已知 .overview .fakelink
是为了替换 .tabbar
选择器的下层结构 a
,所以可以确定 .fakelink
结构一定在 .overview
结构的下一级。
这样所有情况就遍历完成了。
按需求输出的 @extend
像之前说的,使用 @extend 需要规划好可复用的类放在上面,然后再在下面 @extend 调用。这样,我们的可复用部分就会编译输出到 CSS 中,但这些代码可能是无意义的,我们并不想使其编译出现在 CSS 文件中,那么就可以使用 %
符号来实现。
带有 %
符号的选择器不会被编译输出,但是可以被 @extend 到,替换之后输出,例如:
#context a %extreme {
color: blue;
font-weight: bold;
font-size: 2em;
}
.notice {
@extend %extreme;
}
#context .title .notice2 {
@extend %extreme;
}
会编译输出:
#context a .notice,
#context a .title .notice2,
#context .title a .notice2 {
color: blue;
font-weight: bold;
font-size: 2em;
}
跟正常的 @extend 一样。
深入理解 @mixin
@mixin 跟 @extend 命令有点类似,也是极其实用的一个命令,也有一些需要注意的细节。
@mixin 与 @extend 区别
@mixin 定义的是一个片段,这个片段可以是类似变量的一段文字一条属性,也可以是一整个选择器和内容,也可以是一个选择器的一部分 CSS 代码。此外还可以传递参数,通过参数生成不同代码。它需要配合 @inclde 命令来引用这段代码,类似复制的效果。@mixin 定义的内容,不会编译输出。
@extend 就是简单的扩展,基于某个选择器,将其他类似需求的选择器挂靠上,以提高复用程度。
@mixin 常用示例
基础用法:
@mixin red-text {
color: #ff0000;
}
.page-title {
@include red-text;
padding: 4px;
margin-top: 10px;
}
.page-title2 {
@include red-text;
padding: 4px;
font-size: 20px;
}
编译输出:
.page-title {
color: #ff0000;
padding: 4px;
margin-top: 10px;
}
.page-title2 {
color: #ff0000;
padding: 4px;
font-size: 20px;
}
传递参数:
@mixin font($color, $fontSize: 14px) {
color: $color;
font-size: $fontSize;
}
p { @include font(blue); }
h1 { @include font(blue, 20px); }
h2 { @include font($fontSize: 18px,$color: red); }
编译输出:
p {
color: blue;
font-size: 14px;
}
h1 {
color: blue;
font-size: 20px;
}
h2 {
color: red;
font-size: 18px;
}
由此可见,传递参数时,参数跟声明变量一致,如果变量后跟一个值,则表示该参数是可选的,带有默认值。传参时,需要按照参数定义顺序传參,也可以传參时使用参数名和值这样的参数对来传递,这样就无关参数顺序。
@mixin 传递多值参数
一些 CSS3 属性不仅仅只有一个值,还可以有多个值。例如 box-shadow 属性,你可以定义一条 0px 4px 5px #666
来指定它的阴影效果,当然,你也可以指定 box-shadow: 0px 4px 5px #666, 2px 6px 10px #999;
多条属性,来细化定义多个阴影效果。
然而当你定义下面这种 mixin 片段并传值时,报错了:
@mixin box-shadow($shadows) {
-moz-box-shadow: $shadows;
-webkit-box-shadow: $shadows;
box-shadow: $shadows;
}
.shadows {
@include box-shadow(0px 4px 5px #666, 2px 6px 10px #999);
}
由于传递进去的多个值中间被逗号分割,Sass 判定其为两个参数,于是就报错了。解决这个问题,你需要在参数后面加上三个点,表示这个参数可能包含多条属性:
@mixin box-shadow($shadows...) {
-moz-box-shadow: $shadows;
-webkit-box-shadow: $shadows;
box-shadow: $shadows;
}
.shadows {
@include box-shadow(0px 4px 5px #666, 2px 6px 10px #999);
}
这下正常编译出:
.shadows {
-moz-box-shadow: 0px 4px 5px #666, 2px 6px 10px #999;
-webkit-box-shadow: 0px 4px 5px #666, 2px 6px 10px #999;
box-shadow: 0px 4px 5px #666, 2px 6px 10px #999;
}
此外,多值参数还可以用在 @include 传參的时候,分解某个变量值,例如:
@mixin colors($text, $background, $border) {
color: $text;
background-color: $background;
border-color: $border;
}
$values: #ff0000, #00ff00, #0000ff;
.primary {
@include colors($values...);
}
$value-map: (text: #00ff00, background: #0000ff, border: #ff0000);
.secondary {
@include colors($value-map...);
}
编译输出:
.primary {
color: red;
background-color: lime;
border-color: blue;
}
.secondary {
color: lime;
background-color: blue;
border-color: red;
}
在 .primary 中,将 $values 分解变成三个参数传递进去。而在 .secondary 中,将映射型的变量也分解进行了传參。
这个功能对于参数很多的 @mixin 来说,就很方便了。
向 @mixin 传递内容
这个功能绝对必不可少,超级实用,特别是在响应式布局中。先看一下官方的例子:
@mixin apply-to-ie6-only {
* html {
@content;
}
}
@include apply-to-ie6-only {
#logo {
background-image: url(/logo.gif);
}
}
编译后输出:
* html #logo {
background-image: url(/logo.gif);
}
定义的 apply-to-ie6-only 是一个选择器或者一段代码片段,在其中添加了 @content 这个命令。当使用 @include 命令使用这条 mixin 的时候,将一段内容包裹了起来,这段被包裹的内容就会替换掉 @mixin 中的 @content。
简直太碉堡了!
对于 Media Queries 开发就非常方便了,我们可以定义下面这样的代码:
@mixin apply-to-iphone5 {
@media only screen
and (min-device-width : 320px)
and (max-device-width : 568px) {
@content;
}
}
@include apply-to-iphone5 {
#logo {
background-image: url(/[email protected]2x.gif);
}
}
编译后输出:
@media only screen and (min-device-width: 320px) and (max-device-width: 568px) {
#logo {
background-image: url(/[email protected]2x.gif);
}
}
这样在需要 Media Queries 的地方,很快速就插入进去了,而不需要像以前那样在专门的 Media Queries 代码区域编写代码来回跳转。
而目前也推荐这种方式,即直接在相应位置下面编写 Media Querires 代码,这样在后期维护以及代码阅读方面更加简单。
灵活的控制语句
Sass 中也有诸如 @if、@while 等常见的控制语句来实现一些简单的流程控制。这部分主要用于 @function 或者 @mixin 等传递参数的命令,在内部进行判断,提高灵活性。但是普通情况下,也可以应用发挥巨大能量,例如:
@for 循环
就是简单的循环,只能使用数字,每次固定一个步长,例如:
@for $i from 5 through 1 {
.item-#{$i} { width: 2em * $i; }
}
简单的一个循环,输出:
.item-5 {
width: 10em;
}
.item-4 {
width: 8em;
}
.item-3 {
width: 6em;
}
.item-2 {
width: 4em;
}
.item-1 {
width: 2em;
}
对于有规律的批量的 CSS 输出非常方便。注意 @for 命令使用时有一个 through 关键词,这个参数还可以是 to 关键词,至于两者的区别,可以自行试验。
@while 循环
@while 和 @for 命令是非常接近的,只不过 @while 循环可以自己定义步长,例如:
$i: 6;
@while $i > 0 {
.item-#{$i} { width: 2em * $i; }
$i: $i - 2;
}
编译输出:
.item-6 {
width: 12em; }
.item-4 {
width: 8em; }
.item-2 {
width: 4em; }
控制起来更加灵活一点。
@each 遍历循环
这个是最灵活最实用的循环命令了。你可以用它遍历一个列表或者映射型的变量,然后循环输出。例如网站常见的 icon 的 CSS 代码:
.puma-icon {
background-image: url(‘/images/puma.png‘); }
.sea-slug-icon {
background-image: url(‘/images/sea-slug.png‘); }
.egret-icon {
background-image: url(‘/images/egret.png‘); }
.salamander-icon {
background-image: url(‘/images/salamander.png‘); }
通常来说,我们需要复制好几遍,然后依次替换掉关键词,但是有了 each 之后,我们可以遍历关键词循环输出,代码如下:
@each $animal in puma, sea-slug, egret, salamander {
.#{$animal}-icon {
background-image: url(‘/images/#{$animal}.png‘);
}
}
在上面,我们定义了一个 $animal 变量作为临时变量,然后循环 puma、sea-slug、egret、salamander 这四个值,循环体内,调用 #{}
将变量变成字符串插入到 CSS 选择器和需要的地方。
简单的一个循环,就搞定了大量重复的东西。当然,这只是 @each 最基础的用法,这只是一个循环变量,我们还可以增加多个循环变量来构造更加复杂的循环:
@each $animal, $color, $cursor in (puma, black, default),
(sea-slug, blue, pointer),
(egret, white, move) {
.#{$animal}-icon {
background-image: url(‘/images/#{$animal}.png‘);
border: 2px solid $color;
cursor: $cursor;
}
}
上面代码表示循环三个“数组”,三个循环变量分别对应每个组中的相应参数,然后用在循环中,编译输出:
.puma-icon {
background-image: url(‘/images/puma.png‘);
border: 2px solid black;
cursor: default; }
.sea-slug-icon {
background-image: url(‘/images/sea-slug.png‘);
border: 2px solid blue;
cursor: pointer; }
.egret-icon {
background-image: url(‘/images/egret.png‘);
border: 2px solid white;
cursor: move; }
当然,也可以循环映射型的参数:
@each $header, $size in (h1: 2em, h2: 1.5em, h3: 1.2em) {
#{$header} {
font-size: $size;
}
}
输出:
h1 {
font-size: 2em; }
h2 {
font-size: 1.5em; }
h3 {
font-size: 1.2em; }