SCSS describes a person

.human {
  / / or block: b | element: e | modifier: m

  &__finger {
    &--little {}
  }

  // case.1 [b--m nested b__e]
  &--male {
    .human__leg{}}// case.2 [false class or false element nested b__e]
  &:hover {
    .human__hand{}}// case.3 [state nested b__e]
  &.is-hurt {
    .human__head{}}// case.4 [optionally nested +, ~, etc special selectors]
  &__arm {
    &:focus{& ~.human__hand--right{}}}// case.5 [Share rule]
  &__teeth, &__tongue {}
}
Copy the code

Above listed several original style SCSS development often encounter several have to. Block__element -modifier write all several cases, that is, this article wants to solve the BEM development more painful place – the most painful is to add namespace…

Step 1: Declare bem-mixins

// Define the concatenation
$element-separator: '__';
$modifier-separator: The '-';

@mixin b($block) {. # {$block} {
    @content; }}@mixin e($element) {
  $selector: &; // & holds the context, which in mixins actually refers to blocks

  @at-root { // @at-root indicates that the rule is written outside of the nesting. # {$selector+ $element-separator + $element} {
      @content; }}}@mixin m($modifier) {
  $selector: &;

  @at-root# {{.$selector + $modifier-separator + $modifier} {
      @content; }}}Copy the code

So we can write it like this

@include b(human) {
  @include e(finger) {
    @include m(little) {}
  }
}
Copy the code

Step 2: Solve case.1, b–m embedded b__e

// Write case1 directly with mixin above
@include b(human) {
  @include m(male) {
    @include e(leg) {
      Human --male__leg instead of. Human --male. Human__leg}}}Copy the code

Therefore, what we need to do is to judge whether e changes into nested output instead of direct stitching when it is inside M, based on whether there is “–” in the above and below

Converts the context to a string and determines whether it contains--That is$modifier-separator

/* converts to the string */
@function selectorToString($selector) {
  $selector: inspect($selector);
  $selector: str-slice($selector.2, -2);

  @return $selector;
}

/* Check whether there is a modifier-separator */
@function containsModifier($selector) {
  $selector: selectorToString($selector);

  @if str-index($selector.$modifier-separator) {
    @return true;
  } @else {
    @returnfalse; }}Copy the code

rewritee

@mixin e($element) {
  $selector: &;

  @if containsModifer($selector) {
    @at-root {
      #{$selector} {
        // We need a block name. How do we get it? The following discussion. # {[block] + $element-separator + $element} {
          @content; }}}}@else {
    // The original code
    @at-root { // @at-root indicates that the rule is written outside of the nesting. # {$selector+ $element-separator + $element} {
            @content; }}}}Copy the code

Get the block name

Block names can be obtained in two ways:

  1. Either.b–m or.b__e–m in context (via str-index and str-slice implementations of Sass)
  2. The second easy way is to use global variables!
// Simply lock a global variable in a block. Multiple or multiple file compilations do not conflict

$B: ' '; // Store the current block name
$E: ' '; // You can also store element names

@mixin b($block) {
  $B: $block! global;/ / * * *! Global overrides this value to the global variable ***
  // The original code. # {$B} {
    @content; }}// So the sentence in e can be written as
@mixin e($element) {
  $selector: &;
  $E: $element! global;@if containsModifer($selector) {
    @at-root {
      #{$selector# {} {.$B + $element-separator + $element} {
          @content; }}}}... }// case1 can be written like this
@include b(human) {
  @include m(male) {
    @include e(leg) {
     // Human --male. Human__leg}}}Copy the code

Step 3: Solve case.2 (nested b__e in pseudo-classes or pseudo-elements) and case.3 (nested b__e in state)

.human {
  // case.2 [false class or false element nested b__e]
  &:hover {
    .human__hand{}}// case.3 [state nested b__e]
  &.is-hurt {
    .human__head{}}}Copy the code

It is the same as the second step, but the sign of the judgment is different, the judgment is whether there is “:” and “is-” :

// The state prefix is "is-", which needs to be implemented.
$state-prefix: 'is-';

@mixin when($state) {
  @at-root# {{# {and}.$state-prefix + $state} {
      @content; }}}Hover () :hover () :hover ();
Hover () {hover (); hover () {hover (); hover ();
// @incude b(block) {
// &:hover {
// @error &;
// // the & printed here will not be imagined. Block :hover but.block. Block :hover
// // guess because this is the second time to read the context, the first time will be in &:hover, so there is a difference
/ /}
// }
// So there's a wrapper around pseudo, too, with @at-root to reset the & count
@mixin pseudo($pseudo) {
  @at-root# # # {and} {' : {$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) {} } }Copy the code

Step 4: case.4, nested +, ~ and other special selectors in any case

@include b(human) {
  // case.4 [nested +, ~, etc special selectors bem junction]
  &__arm {
    &:focus 
      & + .human__arm{& -left& ~ {}}.human__hand--right{}}}}Copy the code

Solutions:

// This mixin can generate either.b__e directly or.b__e--m
// The order of the parameters is set to (selector, Element, Modifier, Block)
// Since block changes are generally minimal, a default value of $E is sufficient

@mixin spec-selector($specSelector: ' '.$element: $E.$modifier: false, $block: $B) {
  // Determine whether the output is b__e or b__e--m
  @if $modifier {
    $modifierCombo: $modifier-separator + $modifier;
  }  

  @at-root# # {{and} {$specSelector# {}.$block+$element-separator+$element+$modifierCombo} {
      @content}}} // Then write look, bingoooooo@include b(human) {
  @include e(arm) {
    @include pseudo(focus) {
      @include spec-selector('+') {
        // .human__arm:focus + .human__arm
        @include m(left) { // .human__arm:focus + .human__arm--left }
      }
      @include spec-selector('~', hand, right) {
        // .human__arm:focus ~ .human__hand--right}}}}Copy the code

Step 5: Case.5, share rules

.human {
  // case.5 [Share rule]
  &__teeth, &__tongue {}

  // This is interesting. In real development, you will usually encounter shared rules, and in most cases there will be a single definition
  &__teeth {}
  &__tongue {}

  // Create a "share rule" in SASS.
  // Then extend with @extend is best, for example
  // The result is the same. @extend lifts selectors with common rules to form a, b {}
  %shared-rule {}

  &__teeth {
   @extend %shared-rule;
  }

  &__tongue {
   @extend%shared-rule; }}Copy the code

Solutions:

@include b(human) {
  %shared-rule {}

  @include b(teeth) { @extend %shared-rule; }

  @include b(tongue) { @extend %shared-rule;}
}

// This output is not as expected
/ / expectations
.human__teeth..human__tongue {}

// Actual...
.human .human__teeth..human .human__tongue {}


// This is because %shared-rule is defined in.human so the context is brought in
// To solve the problem, it is easy to use @at-root to define two mixins

@mixin share-rule($name) {
  $rule-name: '%shared-'+$name;

  @at-root# {$rule-name} {
    @content}}@mixin extend-rule($name) {
  @extend #{'%shared-'+$name};
}

/ / then!

@include b(human) {
  @include share-rule(skin) {}

  @include b(teeth) { @include extend-rule(skin); }

  @include b(tongue) { @includeextend-rule(skin); }}// *** Note: the name of the shared rule cannot be repeated, otherwise **** will not be overwritten
// *** Note: a list map is required to store existing rule names
// *** note: the specific code is not ****
Copy the code

Final effect

@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) {}
    }
  }
}
Copy the code

case

The document structure

_config.scss

/** * SCSS configuration items: namespace and BEM */
$namespace: 'ab';
$elementSeparator: '__';
$modifierSeparator: The '-';
$state-prefix: 'is-';
Copy the code

_function.scss

/** ** auxiliary function */
@import 'config';

/* converts to the string */
@function selectorToString($selector) {
  $selector: inspect($selector);
  $selector: str-slice($selector.2, -2);

  @return $selector;
}

/* Check whether there is any Modifier */
@function containsModifier($selector) {
  $selector: selectorToString($selector);

  @if str-index($selector.$modifierSeparator) {
    @return true;
  } @else {
    @returnfalse; }}/* Check whether there are pseudo classes */
@function containsPseudo($selector) {
  $selector: selectorToString($selector);

  @if str-index($selector.':') {
    @return true;
  } @else {
    @returnfalse; }}Copy the code

_mixin.scss

/** ** blend macro */
@import 'config';
@import 'function';

/** * BEM */
@mixin b($block) {
  $B: $namespace + The '-' + $block! global; . # {$B} {
    @content; }}/* For pseudo-classes, e is automatically nested under the pseudo-class */
@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; }}}/* For e that needs to be nested under m, call this blend macro, usually when switching the state of the entire component, such as switching colors */
@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; }}}/** ** ** /

/* Single line beyond hidden */
@mixin lineEllipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Multiple lines beyond hidden */
@mixin multiEllipsis($lineNumber: 3) {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: $lineNumber;
  overflow: hidden;
}

/* Clear float */
@mixin clearFloat {
  &::after {
    display: block;
    content: ' ';
    height: 0;
    clear: both;
    overflow: hidden;
    visibility: hidden; }}/* 0.5px border */
@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;
}
Copy the code

Page use

<div class="ab-experi-desc__title"></div>
Copy the code
@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; }}}Copy the code

Reference: zhuanlan.zhihu.com/p/28650879