导读
首先来看一个bem命名示例
.el-message-box{}
.el-message-box__header{}
.el-message-box__header--active{}
如果使用已经封装好的bem方法的话,那么可以写成
@include b('message-box') {
@include e('header') {
@include m('active');
}
}
接下来我们来看一下bem方法是如何实现的
bem方法解析
首先我们找到style/mixins/config.scss
文件,里面定义了如下几个变量
$namespace: 'el';
$element-separator: '__';
$modifier-separator: '--';
$state-prefix: 'is-';
然后我们再找到style/mixins/config.scss
文件,找到b,e,m方法
/* BEM
-------------------------- */
@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;
}
}
}
代码量不多,逻辑也不复杂,但是语法有点晦涩难懂,接下来我们一个一个解释
+ !global
变量提升,将局部变量提升为全局变量,在其他函数体内也能访问到此变量
+ @at-root
将父级选择器直接暴力的改成根选择器
.header{
@at-root {
.content{color:red}
}
}
编译为
.header{}
.content{color:red}
+ #{}
插值,可以通过 #{}
插值语法在选择器和属性名中使用 SassScript
变量
$name: foo;
$attr: border;
p.#{$name} {
#{$attr}-color: blue;
}
编译为
p.foo {
border-color: blue;
}
在大多数情况下,这种做可能还不如使用直接变量来的方便,但使用 #{}意味着靠近它的运算符都将被视为纯CSS
p {
$font-size: 12px;
$line-height: 30px;
font: #{$font-size}/#{$line-height};
}
编译为
p.foo {
font:12px/30px;
}
现在我们在重新看一下b方法,定义了一个全局变量,拼接了一下字符串,逻辑很简单,e方法稍微复杂点,调用了一个hitAllSpecialNestRule
方法(判断父级选择器是否包含'--','is',':'
),hitAllSpecialNestRule定义在style/mixins/function
文件中,代码如下:
@import 'config';
/* BEM support Func
-------------------------- */
// inspect Returns a string representation of $value
//@debug inspect("Helvetica"); unquote('"Helvetica"')
@function selectorToString($selector) {
$selector: inspect($selector);
$selector: str-slice($selector, 2, -2);
@return $selector;
}
// 判断选择器(.el-button__body--active) 是否包含 '--'
@function containsModifier($selector) {
$selector: selectorToString($selector);
@if str-index($selector, $modifier-separator) {
@return true;
} @else {
@return false;
}
}
// 判断选择器(.el-button__body.is-active) 是否包含 'is'
@function containWhenFlag($selector) {
$selector: selectorToString($selector);
@if str-index($selector, '.' + $state-prefix) {
@return true;
} @else {
@return false;
}
}
// 判断选择器(.el-button__body:before) 是否包含伪元素(:hover)
@function containPseudoClass($selector) {
$selector: selectorToString($selector);
@if str-index($selector, ':') {
@return true;
} @else {
@return false;
}
}
// hit:命中 nest:嵌套
@function hitAllSpecialNestRule($selector) {
@return containsModifier($selector) or containWhenFlag($selector) or containPseudoClass($selector);
}
inspect:以字符串的形式返回表达式
@debug meta.inspect(10px 20px 30px); // unquote("10px 20px 30px")
@debug meta.inspect(("width": 200px)); // unquote('("width": 200px)')
@debug meta.inspect(null); // unquote("null")
@debug meta.inspect("Helvetica"); // unquote('"Helvetica"')
Maps
不能转换为纯CSS。作为变量的值或参数传递给CSS函数将会导致错误。使用inspect($value)
函数以产生输出字符串,这对于调试 maps
非常有用。
重新回到e方法中,也是先拼字符串,然后再判断父级class是否存在嵌套关系,然后输出结果。
.container {
@include b('button') {
width: 200px;
height: 200px;
@include e('body') {
color: #ccc;
}
}
}
.container--fix {
@include b('button') {
width: 200px;
height: 200px;
@include e('body') {
color: #ccc;
}
}
}
编译为
.container .el-button {
width: 200px;
height: 200px;
}
.el-button__body {
color: #ccc;
}
.container--fix .el-button {
width: 200px;
height: 200px;
}
.container--fix .el-button .el-button__body {
color: #ccc;
}
最后一个e方法,流程和b一致,区别在拼接currentSelector
字符串时,使用了$
父级选择器,还没有使用全局变量B+全局变量E来拼接,因为结构不一定是B-E-M,有可能是B-M。最后附上完整的编译结果
.container {
@include b('button') {
width: 200px;
height: 200px;
@include e('body') {
color: #ccc;
@include m('success');
}
}
}
.container--fix {
@include b('button') {
width: 200px;
height: 200px;
@include e('body') {
color: #ccc;
@include m('success');
}
}
}
编译为
.container .el-button {
width: 200px;
height: 200px;
}
.el-button__body {
color: #ccc;
}
.container--fix .el-button {
width: 200px;
height: 200px;
}
.container--fix .el-button .el-button__body {
color: #ccc;
}
scss完整代码如下,可以在scss在线编译
中编译调试
$namespace: 'el';
$element-separator: '__';
$modifier-separator: '--';
$state-prefix: 'is-';
@function selectorToString($selector) {
$selector: inspect($selector);
$selector: str-slice($selector, 2, -2);
@return $selector;
}
// 判断选择器(.el-button__body--active) 是否包含 '--'
@function containsModifier($selector) {
$selector: selectorToString($selector);
@if str-index($selector, $modifier-separator) {
@return true;
} @else {
@return false;
}
}
// 判断选择器(.el-button__body.is-active) 是否包含 'is'
@function containWhenFlag($selector) {
$selector: selectorToString($selector);
@if str-index($selector, '.' + $state-prefix) {
@return true;
} @else {
@return false;
}
}
// 判断选择器(.el-button__body.is-active) 是否包含伪元素(:hover)
@function containPseudoClass($selector) {
$selector: selectorToString($selector);
@if str-index($selector, ':') {
@return true;
} @else {
@return false;
}
}
// hit:命中 nest:嵌套
@function hitAllSpecialNestRule($selector) {
@return containsModifier($selector) or containWhenFlag($selector) or containPseudoClass($selector);
}
@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;
}
}
}
@mixin configurable-m($modifier, $E-flag: false) {
$selector: &;
$interpolation: '';
@if $E-flag {
$interpolation: $element-separator + $E-flag;
}
@at-root {
#{$selector} {
.#{$B + $interpolation + $modifier-separator + $modifier} {
@content;
}
}
}
}
@mixin spec-selector($specSelector: '', $element: $E, $modifier: false, $block: $B) {
$modifierCombo: '';
@if $modifier {
$modifierCombo: $modifier-separator + $modifier;
}
@at-root {
#{&}#{$specSelector}.#{$block + $element-separator + $element + $modifierCombo} {
@content;
}
}
}
@mixin meb($modifier: false, $element: $E, $block: $B) {
$selector: &;
$modifierCombo: '';
@if $modifier {
$modifierCombo: $modifier-separator + $modifier;
}
@at-root {
#{$selector} {
.#{$block + $element-separator + $element + $modifierCombo} {
@content;
}
}
}
}
@mixin when($state) {
@at-root {
&.#{$state-prefix + $state} {
@content;
}
}
}
@mixin extend-rule($name) {
@extend #{'%shared-' + $name};
}
@mixin share-rule($name) {
$rule-name: '%shared-' + $name;
@at-root #{$rule-name} {
@content;
}
}
@mixin pseudo($pseudo) {
@at-root #{&}#{':#{$pseudo}'} {
@content;
}
}
.container {
@include b('button') {
width: 200px;
height: 200px;
@include e('body') {
color: #ccc;
@include m('success');
}
}
}
.container--fix {
@include b('button') {
width: 200px;
height: 200px;
@include e('body') {
color: #ccc;
@include m('success');
}
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。