最近在阅读 ElementUI 源码的时候查看样式,发现其中使用了 SCSS Mixins
非常巧妙的实现了 CSS BEM 命名规范,也借此机会了解一下SCSS的高级用法,在此记录分享一下。
BEM 命名法
BEM 即 Block - Element - Modifier 的缩写,它是一套 CSS 命名规范,用于创建可复用的组件样式。
Why BEM
前端现代化的今天,最让人头疼的还是样式的编写,尽管已经有了各种先进的工具,例如 SCSS,PostCSS ,在多人协作开发时,我们还是能经常看到这样的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| .xx-panel {
.xx-panel-header {
.xx-panel-title {
font-size: 20px;
/* more */
}
}
.xx-panel-body {
.xx-panel-content {
/* more */
}
}
}
|
SCSS 的嵌套特性容易被滥用,写出这种很难被修改,无法复用的代码。而且不同的人往往写的风格不同,重构时几乎只能推倒重来。
BEM 就是为了解决 CSS 开发中的各种问题而提出来的。
BEM 是什么
BEM 即 Block - Element - Modifier 的缩写,下面将介绍一下这三个概念:
块 Block
一个块是一个独立的有意义的实体,例如 header
,menu
,panel
;
元素 Element
元素是块的一部分,和块紧密联系,例如 header title
,menu item
,panel header
;
修饰符 Modifier
块或元素的状态,用它来改变被修饰对象的样式或者功能,例如 disabled
,checked
,size big
符号
BEM 使用三种符号来区分这三个概念,分别是
-
表示普通的字符相连__
双下划线,连接 Block 和 Element_
单下划线,连接修饰符
所以一个标准的 BEM 命名的 CSS选择器就像这样:xx-block__element_modifier
How
使用 BEM 命名法改造前面的例子后,代码就变成这样:
1
2
3
4
5
6
7
8
9
10
11
| .xx-panel {
}
.xx-panel_primary {
color: red;
}
.xx-panel__title {
font-size: 20px;
}
.xx-panel__content {
/* more */
}
|
SCSS mixins 实现 BEM
从上面 BEM 的例子都可以感受到,BEM 有个最大的缺点就是要写非常多冗余的代码,尤其是迭代多了以后,代码就会变得又臭又长。
最近在阅读 ElementUI 源码的时候查看样式,发现其中使用了 SCSS Mixins
非常巧妙的实现了 CSS BEM 命名规范,通过 Mixin 的妙用,使得 BEM 的使用变得优雅简洁。先来看看它的使用方法:
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
| @include b(dropdown-menu) {
position: absolute;
top: 0;
left: 0;
z-index: 10;
@include e(item) {
list-style: none;
padding: 0 20px;
margin: 0;
@include m(divided) {
position: relative;
&:before {
content: '';
}
}
@include when(disabled) {
cursor: default;
color: $--font-color-disabled-base;
pointer-events: none;
}
}
@include m(medium) {
padding: 6px 0;
@include e(item) {
line-height: 30px;
padding: 0 17px;
font-size: 14px;
&.el-dropdown-menu__item--divided {
$divided-offset: 6px;
margin-top: $divided-offset;
&:before {
height: $divided-offset;
margin: 0 -17px;
}
}
}
}
@include m(small) {
padding: 6px 0;
@include e(item) {
line-height: 27px;
padding: 0 15px;
font-size: 13px;
&.el-dropdown-menu__item--divided {
$divided-offset: 4px;
margin-top: $divided-offset;
&:before {
height: $divided-offset;
margin: 0 -15px;
}
}
}
}
@include m(mini) {
padding: 3px 0;
@include e(item) {
line-height: 24px;
padding: 0 10px;
font-size: 12px;
&.el-dropdown-menu__item--divided {
$divided-offset: 3px;
margin-top: $divided-offset;
&:before {
height: $divided-offset;
margin: 0 -10px;
}
}
}
}
}
|
源码实现如下:
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
| $namespace: 'el';
$element-separator: '__';
$modifier-sperator: '--';
@mixin b($block) {
$B: $namespace+'-'+$block !global;
.#{$B} {
@content;
}
}
@mixin e($element) {
$E: $element !global;
$selector: &;
$currentSelector: "";
@each $unit in $element {
$currentSelector: #{$currentSelector + "." + $B + $element-separator + $unit + ","};
}
@if hitAllSpecialNestRule($selector) {
@at-root {
#{$selector} {
#{$currentSelector} {
@content;
}
}
}
} @else {
@at-root {
#{$currentSelector} {
@content;
}
}
}
}
@mixin m($modifier) {
$selector: &;
$currentSelector: "";
@each $unit in $modifier {
$currentSelector: #{$currentSelector + & + $modifier-separator + $unit + ","};
}
@at-root {
#{$currentSelector} {
@content;
}
}
}
|
源码解析
构造字符串
#{$var}
这是一个极其实用的 SCSS 语法,他将内部变量转换成字符串使用。
@at-root
@at-root
指令会将内部的选择器展平,暴露到最顶层。例如
1
2
3
4
| .parent {
/* more */
@at-root .child { /* more */ }
}
|
会翻译成
1
2
| .parent { /* more */ }
.child { /* more */ }
|
@content
@content
指令是另外一个很有技巧性的 SCSS 指令。它的作用是将外部的 Content Block 传递到 Mixin 内部去。例如 SCSS 文档中的这个例子
1
2
3
4
5
6
7
8
9
10
| @mixin apply-to-ie6-only {
* html {
@content;
}
}
@include apply-to-ie6-only {
#logo {
background-image: url(/logo.gif);
}
}
|
在 @include apply-to-ie6-only
块内部的内容就会传递到 apply-to-ie6-only
mixin 的 @content
中去,实现这种巧妙的封装形式。
它将翻译为:
1
2
3
| * html #logo {
background-image: url(/logo.gif);
}
|
More
在了解完这三个 SCSS 的知识点后,再看这种使用 mixins
来实现 BEM 命名法的代码就会非常轻松了。
事实上,Element 这套 BEM 命名法已经被抽出来,可以独立与 ElementUI 使用了:waynecz/Watson
Reference