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:
- Either.b–m or.b__e–m in context (via str-index and str-slice implementations of Sass)
- 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