SCSS 描述一个人
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
| .human {
&__finger { &--little {} }
&--male { .human__leg {} }
&:hover { .human__hand {} }
&.is-hurt { .human__head {} }
&__arm { &:focus { & ~ .human__hand--right {} } }
&__teeth, &__tongue {} }
|
上面列举了几种原始画风下 scss
开发时经常会碰到几种不得不把 .block__element--modifier
写全的几种情况,也就是 本文 想解决的BEM
开发比较痛的地方 —— 最痛的是还要加 namespace
…
第一步:声明bem-mixins
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
| $element-separator: '__'; $modifier-separator: '--';
@mixin b($block) { .#{$block} { @content; } }
@mixin e($element) { $selector: &;
@at-root { .#{$selector+ $element-separator + $element} { @content; } } }
@mixin m($modifier) { $selector: &;
@at-root { .#{$selector + $modifier-separator + $modifier} { @content; } } }
|
这样子我们就可以像下面的写法
1 2 3 4 5
| @include b(human) { @include e(finger) { @include m(little) {} } }
|
第二步:解决 case.1,b–m 内嵌 b__e
1 2 3 4 5 6 7 8
| @include b(human) { @include m(male) { @include e(leg) { } } }
|
所以我们要做的就是判断当 e
在 m
内部的时候改变成嵌套输出
而不是直接拼接
,可以以上下文中是否存在「--」
为依据来判断
将上下文转换成字符串并判断是否包含 --
也就是 $modifier-separator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @function selectorToString($selector) { $selector: inspect($selector); $selector: str-slice($selector, 2, -2);
@return $selector; }
@function containsModifier($selector) { $selector: selectorToString($selector);
@if str-index($selector, $modifier-separator) { @return true; } @else { @return false; } }
|
改写 e
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @mixin e($element) { $selector: &;
@if containsModifer($selector) { @at-root { #{$selector} { .#{[block名] + $element-separator + $element} { @content; } } } } @else { @at-root { .#{$selector+ $element-separator + $element} { @content; } } } }
|
block 名的获取
block 名的获取可以想到两种办法:
- 一种是根据上下文中的 .b–m 或者 .b__e–m 去进行字符串切割(通过 sass 的 str-index 和 str-slice 实现)
- 第二种简单的办法,利用全局变量实现!
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
|
$B: ''; $E: '';
@mixin b($block) { $B: $block !global; .#{$B} { @content; } }
@mixin e($element) { $selector: &; $E: $element !global; @if containsModifer($selector) { @at-root { #{$selector} { .#{$B + $element-separator + $element} { @content; } } } } ... }
@include b(human) { @include m(male) { @include e(leg) { } } }
|
第三步:解决 case.2(伪类或者伪元素中嵌套 b__e)和 case.3(state 中嵌套 b__e)
1 2 3 4 5 6 7 8 9 10
| .human { &:hover { .human__hand {} } &.is-hurt { .human__head {} } }
|
跟 第二步 是一个道理,只不过判断的标志不同,判断是否存在「:」和 「is-」而已: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
| $state-prefix: 'is-';
@mixin when($state) { @at-root { #{&}.#{$state-prefix + $state} { @content; } } }
@mixin pseudo($pseudo) { @at-root #{&}#{':#{$pseudo}'} { @content } }
@function containWhenFlag($selector) { $selector: selectorToString($selector);
@if str-index($selector, '.' + $state-prefix) { ... } }
@function containPseudoClass($selector) { $selector: selectorToString($selector);
@if str-index($selector, ':') { ... } }
@include b(human) { @include when(hurt) { @include e(hand) {} } @include pseudo(hover) { @include e(head) {} } }
|
第四步:case.4,任意情况下嵌套 +, ~ 等特殊的选择符1 2 3 4 5 6 7 8 9 10 11
| @include b(human) { &__arm { &:focus & + .human__arm { &--left {} } & ~ .human__hand--right {} } } }
|
解决方法: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
|
@mixin spec-selector($specSelector: '', $element: $E,$modifier: false, $block: $B) { @if $modifier { $modifierCombo: $modifier-separator + $modifier; }
@at-root { #{&}#{$specSelector}.#{$block+$element-separator+$element+$modifierCombo} { @content } } }
// 然后写写看,bingoooooo @include b(human) { @include e(arm) { @include pseudo(focus) { @include spec-selector('+') { @include m(left) { } @include spec-selector('~', hand, right) { } } } }
|
第五步:case.5,共享规则1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| .human { &__teeth, &__tongue {}
&__teeth {} &__tongue {}
%shared-rule {}
&__teeth { @extend %shared-rule; }
&__tongue { @extend %shared-rule; } }
|
解决方法: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
| @include b(human) { %shared-rule {}
@include b(teeth) { @extend %shared-rule; }
@include b(tongue) { @extend %shared-rule;} }
.human__teeth, .human__tongue {}
.human .human__teeth, .human .human__tongue {}
@mixin share-rule($name) { $rule-name: '%shared-'+$name;
@at-root #{$rule-name} { @content } }
@mixin extend-rule($name) { @extend #{'%shared-'+$name}; }
@include b(human) { @include share-rule(skin) {}
@include b(teeth) { @include extend-rule(skin); }
@include b(tongue) { @include extend-rule(skin); } }
|
最终实现效果
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
| @include b(human) { @include e(finger) { @include m(little) {} }
@include m(male) { @include e(leg) {} }
@include pseudo(hover) { @include e(hand) {} }
@include when(hurt) { @include e(hand) {} }
@include e(arm) { @include pseudo(focus) { @include spec-selector('+') { @include m(left) {} } @include spec-selector('~', hand, right) {} } } }
|
案例
文档结构

_config.scss
1 2 3 4 5 6 7
|
$namespace: 'ab'; $elementSeparator: '__'; $modifierSeparator: '--'; $state-prefix: 'is-';
|
_function.scss
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
|
@import 'config';
@function selectorToString($selector) { $selector: inspect($selector); $selector: str-slice($selector, 2, -2);
@return $selector; }
@function containsModifier($selector) { $selector: selectorToString($selector);
@if str-index($selector, $modifierSeparator) { @return true; } @else { @return false; } }
@function containsPseudo($selector) { $selector: selectorToString($selector);
@if str-index($selector, ':') { @return true; } @else { @return false; } }
|
_mixin.scss
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
@import 'config'; @import 'function';
@mixin b($block) { $B: $namespace + '-' + $block !global;
.#{$B} { @content; } }
@mixin e($element...) { $selector: &; $selectors: '';
@if containsPseudo($selector) { @each $item in $element { $selectors: #{$selectors + '.' + $B + $elementSeparator + $item + ','}; } @at-root { #{$selector} { #{$selectors} { @content; } } } } @else { @each $item in $element { $selectors: #{$selectors + $selector + $elementSeparator + $item + ','}; } @at-root { #{$selectors} { @content; } } } } @mixin m($modifier...) { $selectors: ''; @each $item in $modifier { $selectors: #{$selectors + & + $modifierSeparator + $item + ','}; }
@at-root { #{$selectors} { @content; } } }
@mixin me($element...) { $selector: &; $selectors: '';
@if containsModifier($selector) { @each $item in $element { $selectors: #{$selectors + '.' + $B + $elementSeparator + $item + ','}; } @at-root { #{$selector} { #{$selectors} { @content; } } } } @else { @each $item in $element { $selectors: #{$selectors + $selector + $elementSeparator + $item + ','}; } @at-root { #{$selectors} { @content; } } } }
@mixin when($state) { @at-root { &.#{$state-prefix + $state} { @content; } } }
@mixin lineEllipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@mixin multiEllipsis($lineNumber: 3) { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: $lineNumber; overflow: hidden; }
@mixin clearFloat { &::after { display: block; content: ''; height: 0; clear: both; overflow: hidden; visibility: hidden; } }
@mixin halfPixelBorder($direction: 'bottom', $left: 0) { &::after { position: absolute; display: block; content: ''; width: 100%; height: 1px; left: $left; @if ($direction == 'bottom') { bottom: 0; } @else { top: 0; } transform: scaleY(0.5); background: $-color-border-light; } }
@mixin buttonClear { outline: none; -webkit-appearance: none; -webkit-tap-highlight-color: transparent; background: transparent; }
|
页面中使用
1
| <div class="ab-experi-desc__title"></div>
|
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
| @import '../../../assets/css/abstracts/mixin'; @include b(experi-desc) { margin: 6px 0 200px; padding: 0 50px; overflow: hidden;
@include e(title) { position: relative; left: -4px; margin-top: 20px; font-size: 18px; font-weight: 600; color: #333; }
@include e(table) { margin-top: 10px; margin-left: 10px; font-size: 0; border: solid 1px #e5e8ed; background-color: #ffffff;
@include m(no) { line-height: 60px; text-align: center; font-size: 14px; color: #9ca7b6; } }
@include e(tr) { padding: 12px 0; font-size: 12px; border-bottom: 1px solid #e5e8ed; background: #f5f7f9;
>span,img { display: inline-block; width: 12.5%; color: #657180; text-align: center; } } }
|
参考:https://zhuanlan.zhihu.com/p/28650879