首先來看一個bem命名示例css
.el-message-box{} .el-message-box__header{} .el-message-box__header--active{}
若是使用已經封裝好的bem方法的話,那麼能夠寫成sass
@include b('message-box') { @include e('header') { @include m('active'); } }
接下來咱們來看一下bem方法是如何實現的函數
首先咱們找到style/mixins/config.scss
文件,裏面定義了以下幾個變量spa
$namespace: 'el'; $element-separator: '__'; $modifier-separator: '--'; $state-prefix: 'is-';
而後咱們再找到style/mixins/config.scss
文件,找到b,e,m方法debug
/* 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} } }
編譯爲code
.header{} .content{color:red}
+ #{}
插值,能夠經過 #{}
插值語法在選擇器和屬性名中使用 SassScript
變量ip
$name: foo; $attr: border; p.#{$name} { #{$attr}-color: blue; }
編譯爲ci
p.foo { border-color: blue; }
在大多數狀況下,這種作可能還不如使用直接變量來的方便,但使用 #{}意味着靠近它的運算符都將被視爲純CSSelement
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'); } } }