最近在深入学习Sass
的时候,看到了这样一段实现多皮肤的样式代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| $themes: ( default: ( background-color: #000, ), light: ( background-color: #fff, ), ); @mixin themeify { @each $theme-name, $theme-map in $themes { $theme-map: $theme-map !global; body[data-theme='#{$theme-name}'] & { @content; } } }
@function themed($key) { @return map-get($theme-map, $key); } .content { position: relative; @include themeify() { background: themed('background-color'); } }
|
短短的十几行代码却大有乾坤,涵盖了诸多Sass
的知识点。这段样式编译成的css
如下:
1 2 3 4 5 6
| .content { position: relative; } body[data-theme='default'] .content { background: #000; } body[data-theme='light'] .content { background: #fff; }
|
就这样,我们得到了两套不同标签下的背景色样式。那它的实现思路是怎样的?又应用了哪些Sass
的规则特性?待我一点点拆开细说。
变量$
Sass
中使用$
符号声明变量方便管理和使用,变量值可以是数字
、字符串
、颜色值
、布尔值
、空值(null)
、数组
(list,以空格或逗号分隔)以及maps
(相当于对象,用圆括号包裹键值对)。像上文的变量$themes
就是一个maps
,定义了多个主题和其对应的样式集合。
变量定义好之后直接在需要的样式中使用:
1 2 3 4 5 6 7 8 9
| $font-color: red;
.artical { color: $font-color; }
.artical { color: red; }
|
插值语句#{}
通过 #{}
插值语句可以在选择器或属性名中使用变量:
1 2 3 4 5 6 7 8 9
| $name: foo; $attr: border; p.#{$name} { #{$attr}-color: blue; }
p.foo { border-color: blue; }
|
作用域
Sass
变量的作用域只能在当前的层级上有效果,内部定义的变量无法在外部使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| $font-color: red;
.artical { $font-color: blue; color: $font-color; }
.title { color: $font-color; }
.artical { color: blue; }
.title { color: red; }
|
全局关键词!global
!global
用以将变量声明为全局。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| $font-color: red;
.artical { $font-color: blue!global; color: $font-color; }
.title { color: $font-color; }
.artical { color: blue; }
.title { color: blue; }
|
嵌套规则
编写普通css
时我们经常会为一些有包含关系的样式重复输入父选择器,Sass
提供的嵌套规则为我们省去这种烦恼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| .content{ background: #fff; .box{ color: #000; .title{ font-size: 24px; } } }
.content { background: #fff; } .content .box { color: #000; } .content .box .title { font-size: 24px; }
|
Sass
的编译机制会在遇到嵌套规则块时逐层解套,并将外层的父选择器添加到内层的选择器前,通过空格连接构成后代选择器。
父选择器标识符&
写过Sass
的人应该知道当我们想给一个元素定义伪类选择器的时候是这样写的:
1 2 3 4 5 6 7 8 9 10 11 12
| .link { color: blue; &:hover { color: red; } }
.link { color: blue; } .link:hover { color: red; }
|
&
符号在Sass中
被称为父选择器标识符。重点来了:
当包含&的嵌套规则块被打开时,它不会像后代选择器那样进行拼接,而是&被父选择器直接替换。如果含有多层嵌套,最外层的父选择器会一层一层向下传递。
因此,当&
前有其他选择器时,即便是在嵌套内层,编译之后该选择器也不会成为后代选择器:
1 2 3 4 5 6 7 8 9 10 11 12
| .link { color: blue; .wrap & { color: red; } }
.link { color: blue; } .wrap .link { color: red; }
|
这对于我们想要声明一些特殊情况下的样式时非常有帮助,也是文章开头实现多皮肤的关键。
函数
Sass
定义了非常丰富的函数供不同需求使用,比如针对maps
变量可以通过map-get($map, $key)
方法获取属性值:
1 2 3
| $font-weights: ("regular": 400, "medium": 500, "bold": 700);
map-get($font-weights, "medium");
|
map-has-key($map, $key)
检查是否含有某个属性
map-keys($map)
获取属性名集合
map-merge($map1, $map2)
合并两个maps
map-values($map)
获取属性值…等等
更多函数方法看这里。
@function
Sass
还支持自定义函数,可传入参数:
1 2 3 4 5 6 7 8 9 10 11
| $grid-width: 40px;
@function grid-width($n) { @return $n * $grid-width; }
#sidebar { width: grid-width(5); }
#sidebar { width: 200px; }
|
@mixin和@include
@mixin
(混合样式)用于定义一段可以重复使用的样式,并且可以接受参数引入变量,参数支持定义默认值。使用时通过@include
引入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @mixin border($color, $width: 1px, $style: solid) { border: $width $style $color; }
.artical { @include border(#ccc); }
.artical { border: 1px solid #ccc; }
.artical { @include border($width: 2px,$color:#ccc); }
.artical { border: 2px solid #ccc; }
|
不仅仅是传参,在引用混合样式的时候,可以先将一段代码导入到混合指令中,然后再输出混合样式,额外导入的部分将出现在 @content
标志的地方:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @mixin border($color, $width: 1px, $style: solid) { border: $width $style $color; @content; }
.artical { @include border(#ccc) { .title { color: #000; } } }
.artical { border: 1px solid #ccc; } .artical .title { color: #000; }
|
@each遍历
@each
指令的格式是 $var in <list>
, $var
可以是任何变量名,比如 $length
或者 $name
,而<list>
是一连串的值,也就是值列表。$var
的数量可以是一个或者多个,取决于list
中每一组值的数量。@each
对于遍历maps
变量同样适用,$var
对应键名和键值。
且看搬运自官网的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| @each $animal in puma, sea-slug, egret, salamander { .#{$animal}-icon { background-image: url('/images/#{$animal}.png'); } }
.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 $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; }
|
拨云见日
介绍完了这么多的知识点,开头的需求是如何实现的就很明了了。
首先定义几组主题和对应的样式:
1 2 3 4 5 6 7 8
| $themes: ( default: ( background-color: #000, ), light: ( background-color: #fff, ), );
|
在混入样式中遍历主题,并将各组主题样式声明为全局变量,以便导入混入样式时可通过自定义函数获取属性值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @mixin themeify { @each $theme-name, $theme-map in $themes { $theme-map: $theme-map !global; body[data-theme='#{$theme-name}'] & { @content; } } }
@function themed($key) { @return map-get($theme-map, $key); } .content { position: relative; @include themeify() { background: themed('background-color'); } }
|
有了这些基础再通过插值语句#{}
和父选择器标识符&
生成body
标签不同的主题属性选择器样式就最终实现了多皮肤的需求,给强大的Sass
竖大拇指!