preface

I have analyzed the element-UI project (self-built VUE component AIR-UI (2) — first analyze the Element UI project), including the overall idea of directory structure, construction and project development. Today, I intend to focus on the source code of the element-UI project. Let’s talk about how we should design the CSS development specification when developing a UI component. I think element-UI encapsulates it very well. It encapsulates what it abstracts and encapsulates what it encapsulates. So WHEN I developed air-UI, I just moved it over.

What is the BEM

For more information about BEM, see its website: BEM website

BEM stands for Block Element Modifier, which helps you create reusable front-end components and front-end code. BEM has the following three features:

  • reuseOrganizing individual blocks differently and re-using them intelligently reduces the amount of CSS code that must be maintained. With a series of style guides, you can build a block library to make your CSS super efficient.
  • Unit sexBlock styles never depend on other elements on the page, so you never run into cascading problems. You can also transfer blocks from completed projects to new projects.
  • structuredThe BEM method makes your CSS code more structured and easier to understand.

Using THE BEM specification to name CSS and organize the structure of HTML selectors facilitates the maintenance of CSS code and makes the code structure clearer. Of course, there are drawbacks, such as slightly longer names, and since most are one-tier structures, there are style overwrites

How do I use BEM

  1. A separate (semantically or visually) part that can be reused without dependence on other components, as a Block
  2. Part of a block that can be used as an Element
  3. Used as a Modifier to modify a block or element that reflects, for example, its appearance, behavior, or state.

Use of Element UI

Next I’ll talk about how Element-UI takes advantage of SASS to write readable and maintainable CSS code with BEM rules.

Namespace definition

In – chalk/SRC/package/theme mixins/config SCSS this file, there is a specification for BEM definition:

$namespace: 'el';
$element-separator: '__';
$modifier-separator: The '-';
$state-prefix: 'is-';
Copy the code
  1. Double underline__As the interval between blocks and elements, for exampleel-form-item__content
  2. In the double line--To be the interval between a block and a modifier or an element and a modifier, for exampleel-form--inline
  3. In the line-As a block element | | decorator name more words in the interval
  4. The state prefix is usedis, such as whether it is selected or notis-checked

$namespace = BEM; $namespace = BEM; That’s it.

Definition of BEM in SASS

In – chalk/SRC/packages/theme mixins/mixins SCSS have a macro definition of BEM:

The definition of the block

@mixin b($block) {
  $B: $namespace+The '-'+$block! global; . # {$B} {@content; }}Copy the code

A block is a part of a design or layout that has some meaning. Use the namespace EL with a dash, and pass in the name of the block. The style of building blocks, such as the Alert component, is el-alert after rendering to demonstrate its uniqueness. Inside the block, you write the other style code associated with the block.

Notice two details here:

  • ! globalRepresents variable promotion, will local variablesBPromote to a global variable so that it can be accessed by other functions (so that e in B can access B’s selector)B)
  • # {}Represents interpolation, which can be passed# {}Interpolation syntax is used in selector and attribute namesSassScriptThe variable, for example, originally looks like this:
$name: foo;
$attr: border;
p. # {$name#} {{$attr} -color: blue;
}
Copy the code

When compiled, it becomes:

p.foo {
  border-color: blue;
}
Copy the code

The definition of element

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

Each is easy to understand because it is possible to pass multiple arguments, such as in a table, like this:

@include b(table) {
  position: relative;
  // something table content
  @include e((header-wrapper, body-wrapper, footer-wrapper)) {
    width: 100%; }}Copy the code

Compiling to CSS is:

.el-table__body-wrapper..el-table__footer-wrapper..el-table__header-wrapper {
    width: 100%
}
Copy the code

Note that @at-root changes the parent selector to the root selector by force.

Next, we’ll look at the if and else branches. The reason why there’s a branch of hitAllSpecialNestRule is because when you’re nesting an element element in a modifier or other mixin (such as a pseudo-class or state), the modifier comes first. So we use the hitAllSpecialNestRule function to determine if there is a special nesting, and if there is, we’ll nest element inside it, and if there isn’t, we’ll print it as is (instead of the root selector).

Then see hitAllSpecialNestRule definition, in – chalk/SRC/packages/theme mixins/function. The SCSS

@import "config";

/* BEM support Func -------------------------- */
@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;
  }
}

@function containWhenFlag($selector) {
  $selector: selectorToString($selector);
  @if str-index($selector.'. ' + $state-prefix) {
    @return true
  } @else {
    @return false
  }
}

@function containPseudoClass($selector) {
  $selector: selectorToString($selector);
  @if str-index($selector.':') {
    @return true
  } @else {
    @return false
  }
}

@function hitAllSpecialNestRule($selector) {
  @return containsModifier($selector) or containWhenFlag($selector) or containPseudoClass($selector);
}

Copy the code

The first function, selectorToString, converts our selector to a string, and the next three functions check it:

  1. Are there any modifiers? Yesm, whether the superior selector contains the mark asm--substring
  2. Is there a flag, for example.is-checked, through the superior selector whether containsis-substring
  3. Whether there are pseudo classes, for example:hover, through the superior selector whether contains:substring

Finally, the results are returned together to avoid nesting.

M nested e

For example, message-box.scss is centered:

@include b(message-box) {
  / /...
  / / centerAlign layout
  @include m(center) {
    / /...
    @include e(header) {
      padding-top: 30px; }}}Copy the code

Compiling to CSS is:

.el-message-box--center .el-message-box__header {
    padding-top: 30px
}
Copy the code

You can see it’s nested

Contains state B nested e

For example, when step. SCSS is displayed horizontally, this situation will occur:

@include b(step) {
  / /...
  @include when(horizontal) {
    / /...
    @include e(line) {
      height: 2px;
      top: 11px;
      left: 0;
      right: 0; }}}Copy the code

Compiling to CSS is:

.el-step.is-horizontal .el-step__line {
    height: 2px;
    top: 11px;
    left: 0;
    right: 0
}
Copy the code

When is also a custom macro used to add state:

@mixin when($state) {
  @at-root {
    &.#{$state-prefix + $state} {@content; }}}Copy the code
Contains pseudoclass B nested e

For example, this can also happen in step. SCSS:

@include b(step) {
  / /...
  @include pseudo(last-of-type) {
    @include e(line) {
      display: none; }}}Copy the code

Compiling to CSS is:

.el-step:last-of-type .el-step__line {
    display: none
}
Copy the code

Pseudo is also a custom macro used to add pseudo-class state:

@mixin pseudo($pseudo) {
  @at-root #{&}#{':#{$pseudo}'} {
    @content
  }
}
Copy the code

The definition of the modifier

The definition of m is easier to understand than the definition of e:

@mixin m($modifier) {
  $selector: &;
  $currentSelector: "";
  @each $unit in $modifier {
    $currentSelector: # {$currentSelector + & + $modifier-separator + $unit + ","};
  }

  @at-root {
    #{$currentSelector} {@content; }}}Copy the code

One thing to notice here is that when we concatenate the currentSelector string, we’re using the $parent selector instead of using the global variable B plus the global variable E, because the structure doesn’t have to be B-e-m, it could be B-M.

It is also possible to define more than one m at a time, such as in table. SCSS:

@include b(table) {
  @include m((group, border)) {
    border: $--table-border; }}Copy the code

Compiling to CSS is:

.el-table--border..el-table--group {
    border: 1px solid #EBEEF5
}
Copy the code

Basically, the analysis of BEM is only these, the next is in the specific writing of components, skilled application. Let’s review with an example.

BEM example

To write the example, you can compile the simulation directly in SCSS, which is very convenient. First, you can pre-fill in some parameters and use the macro definition.

$namespace: 'el';
$element-separator: '__';
$modifier-separator: The '-';
$state-prefix: 'is-';

@mixin b($block) {
  $B: $namespace+The '-'+$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; }}}//@debug inspect("Helvetica"); unquote('"Helvetica"')
@function selectorToString($selector) {
    $selector: inspect($selector);
    $selector: str-slice($selector.2, -2);
    @return $selector;
}
// Check whether the selector (.el-button__body--active) contains '--'
@function containsModifier($selector) {
    $selector: selectorToString($selector);
    @if str-index($selector.$modifier-separator) {
        @return true;
    } @else{ @return false; }}// Check whether the selector (.el-button__body.is-active) contains 'is'
@function containWhenFlag($selector) {
    $selector: selectorToString($selector);
    @if str-index($selector.'. ' + $state-prefix) {
        @return true;
    } @else{ @return false; }}// Check whether the selector (.el-button__body:before) contains false elements (:hover)
@function containPseudoClass($selector) {
    $selector: selectorToString($selector);
    @if str-index($selector.':') {
        @return true;
    } @else{ @return false; }}// hit: hits nest: nested
@function hitAllSpecialNestRule($selector) {
    @return containsModifier($selector) or containWhenFlag($selector) or containPseudoClass($selector);
}
Copy the code

Now you can write the sASS code directly below:

.container{@include b('button') {
        width: 210px;
        height: 200px;
        @include e('body') {
            color: #ccc;
            @include m('success') {background-color: #fff; }}}}.container--fix{@include b('button') {
        width: 200px;
        height: 200px;
        @include e('body') {
            color: #fff;
            @include m('success') {background-color: #fff; }}}}Copy the code

The corresponding CSS code is generated on the right:

Macro RES for responsive layout

In fact, element-UI defines few macros and functions in SASS, most of which are for BEM service. There is only one macro res for reactive layout:

@mixin res($key.$map: $--breakpoints) {
  // Loop breakpoint Map, return if present
  @if map-has-key($map.$key) {@media only screen and #{inspect(map-get($map.$key))} {@content; }} @else{@warn "Undefeined points: `#{$map}`"; }}Copy the code

If you have used Element-UI before, you should know that when using col components, it is possible to set different response sizes for different screen sizes, such as this one:

<el-row :gutter="10">
  <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="1"><div class="grid-content bg-purple"></div></el-col>
  <el-col :xs="4" :sm="6" :md="8" :lg="9" :xl="11"><div class="grid-content bg-purple-light"></div></el-col>
  <el-col :xs="4" :sm="6" :md="8" :lg="9" :xl="11"><div class="grid-content bg-purple"></div></el-col>
  <el-col :xs="8" :sm="6" :md="4" :lg="3" :xl="1"><div class="grid-content bg-purple-light"></div></el-col>
</el-row>
Copy the code

This is done with the res macro:

@include res(lg) {
  .el-col-lg-0 {
    display: none;
  }
  @for $i from 0 through 24 {
    .el-col-lg-# {$i} {
      width: (1 / 24 * $i * 100) * 1%;
    }

    .el-col-lg-offset-# {$i} {
      margin-left: (1 / 24 * $i * 100) * 1%;
    }

    .el-col-lg-pull-# {$i} {
      position: relative;
      right: (1 / 24 * $i * 100) * 1%;
    }

    .el-col-lg-push-# {$i} {
      position: relative;
      left: (1 / 24 * $i * 100) * 1%; }}}Copy the code

Then, by combining these specified parameters, different media query styles can be implemented:

$--sm: 768px! default;$--md: 992px! default;$--lg: 1200px! default;$--xl: 1920px! default;$--breakpoints: (
  'xs' : (max-width: $--sm - 1),
  'sm' : (min-width: $--sm),
  'md' : (min-width: $--md),
  'lg' : (min-width: $--lg),
  'xl' : (min-width: $--xl));Copy the code

We can implement it online:

Parameter customization var.scss

All customizable parameters in element-UI are in the var. SCSS file. This file is also the key to customizing the theme later, because if you just rewrite some of the parameters of this file and repackage them as CSS, you can create a new theme. We’ll talk about that in a later article


Series of articles:

  • Air-ui (1) — Why do I need to build an Element UI component
  • Self-built VUE component AIR-UI (2) — Take a look at the Element UI project
  • Self-built VUE component AIR-UI (3) – CSS development specification
  • Air-ui (4) — Air-UI environment setup and directory structure
  • Air-ui (5) — Create the first vUE component, Button
  • Self-built VUE component AIR-UI (6) – Creates built-in service components
  • Build vUE component AIR-UI (7) – Create command component
  • Self-built VUE component AIR-UI (8) — Implementation part introduces components
  • Build your own VUE component air-UI (9) — document with Vuepress
  • Air-ui (10) — Vuepress Documentation (Advanced version)
  • Vue Component Air-UI (11) — Vuepress Documentation (Crawl version)
  • Self-built VUE component AIR-UI (12) — Internationalization mechanism
  • Self-built VUE Component AIR-UI (13) — Internationalization Mechanism (Advanced Version)
  • Self-built VUE component AIR-UI (14) — Packaged Build (Dev and Dist)
  • Self-built VUE component AIR-UI (15) — Theme customization
  • Self-built VUE component AIR-UI (16) – Packages to build pub tasks
  • Build your own VUE component AIR-UI (17) – Develop a pit crawl and summary