Vue

element-ui源码阅读-样式

2021-04-24  本文已影响0人  写前端的大叔

element-ui中的样式文件全部在theme-chalk文件夹下,其目录结果主要包括commonfontsmixins和一些组件样式文件,组件样式文件除了日期组件是一个文件夹外,其它的都是一个独立的.scss文件。其目录结构如下所示:

├── README.md
├── gulpfile.js
├── package.json
└── src
    ├── alert.scss
    ├── aside.scss
    ├── autocomplete.scss
    ├── avatar.scss
    ├── backtop.scss
    ├── badge.scss
    ├── base.scss
    ├── breadcrumb-item.scss
    ├── breadcrumb.scss
    ├── button-group.scss
    ├── button.scss
    ├── calendar.scss
    ├── card.scss
    ├── carousel-item.scss
    ├── carousel.scss
    ├── cascader-panel.scss
    ├── cascader.scss
    ├── checkbox-button.scss
    ├── checkbox-group.scss
    ├── checkbox.scss
    ├── col.scss
    ├── collapse-item.scss
    ├── collapse.scss
    ├── color-picker.scss
    ├── common
    │   ├── popup.scss
    │   ├── transition.scss
    │   └── var.scss
    ├── container.scss
    ├── date-picker
    │   ├── date-picker.scss
    │   ├── date-range-picker.scss
    │   ├── date-table.scss
    │   ├── month-table.scss
    │   ├── picker-panel.scss
    │   ├── picker.scss
    │   ├── time-picker.scss
    │   ├── time-range-picker.scss
    │   ├── time-spinner.scss
    │   └── year-table.scss
    ├── date-picker.scss
    ├── dialog.scss
    ├── display.scss
    ├── divider.scss
    ├── drawer.scss
    ├── dropdown-item.scss
    ├── dropdown-menu.scss
    ├── dropdown.scss
    ├── fonts
    │   ├── element-icons.ttf
    │   └── element-icons.woff
    ├── footer.scss
    ├── form-item.scss
    ├── form.scss
    ├── header.scss
    ├── icon.scss
    ├── image.scss
    ├── index.scss
    ├── infinite-scroll.scss
    ├── infiniteScroll.scss
    ├── input-number.scss
    ├── input.scss
    ├── link.scss
    ├── loading.scss
    ├── main.scss
    ├── menu-item-group.scss
    ├── menu-item.scss
    ├── menu.scss
    ├── message-box.scss
    ├── message.scss
    ├── mixins
    │   ├── _button.scss
    │   ├── config.scss
    │   ├── function.scss
    │   ├── mixins.scss
    │   └── utils.scss
    ├── notification.scss
    ├── option-group.scss
    ├── option.scss
    ├── page-header.scss
    ├── pagination.scss
    ├── popconfirm.scss
    ├── popover.scss
    ├── popper.scss
    ├── progress.scss
    ├── radio-button.scss
    ├── radio-group.scss
    ├── radio.scss
    ├── rate.scss
    ├── reset.scss
    ├── row.scss
    ├── scrollbar.scss
    ├── select-dropdown.scss
    ├── select.scss
    ├── slider.scss
    ├── spinner.scss
    ├── step.scss
    ├── steps.scss
    ├── submenu.scss
    ├── switch.scss
    ├── tab-pane.scss
    ├── table-column.scss
    ├── table.scss
    ├── tabs.scss
    ├── tag.scss
    ├── time-picker.scss
    ├── time-select.scss
    ├── timeline-item.scss
    ├── timeline.scss
    ├── tooltip.scss
    ├── transfer.scss
    ├── tree.scss
    └── upload.scss

在阅读element-ui中的样式代码时,需要先了解下scssBEM,下面通过element-ui中实际的代码来介绍下这两个技术点的作用和用途。

1. scss

Sass是成熟、稳定、强大的CSS预处理器,而SCSSSass3版本当中引入的新语法特性,完全兼容CSS3的同时继承了Sass强大的动态功能。

1.1 变量

SCSS中可以将反复使用的CSS样式定义成一个变量来引用它,无需写重复的CSS样式,比如我们可以定义一些常用的颜色字体大小边框等。如element-ui源码中,在common目录下有一个var.scss文件,定义的全部是一些变量,如果需要修改主题,只需要修改该文件中的变量值就可以了,大大的提升了代码的复用性和可维护性。以下挑了几个很常见的变量,应该不陌生,如下所示:

$--color-primary: #409EFF !default;
/// color|1|Background Color|4
$--color-white: #FFFFFF !default;
/// color|1|Background Color|4
$--color-success: #67C23A !default;
/// color|1|Functional Color|1
$--color-warning: #E6A23C !default;
/// color|1|Functional Color|1
$--color-danger: #F56C6C !default;
/// color|1|Functional Color|1
$--color-info: #909399 !default;

scss使用$符号来标识变量,然后再需要使用的地方直接使用既可。如 color:$--color-primary

1.2导入scss文件

SCSS文件中需要导入其它文件时,直接使用@import进行导入。如下所示:

@import "mixins/mixins";
@import "common/var";

1.3混合器

单独的使用变量来定义一些重复的代码是远远不够的,因为变量只能简单的定义一些颜色,字体等,而不能定义重复的代码块,这时就得需要混合器来定义代码块了。混合器使用@mixin标识符定义,这样就可以轻易地通过引用这个名字重用样式代码了,如element-ui源码中,在mixin目录下有一些.scss文件,定义的全部是一些混合器。如在utils.scss文件中,有如下一个混合器,不用设置显示成一行的样式:

@mixin utils-ellipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

定义好后,在需要使用的地方使用include来引用,如下所示:

&.el-checkbox .el-checkbox__label {
      width: 100%;
      @include utils-ellipsis;
      display: block;
      box-sizing: border-box;
      padding-left: 24px;
      line-height: $--transfer-item-height;
    }

混合器并不一定总得生成相同的样式。可以通过在@include混合器时给混合器传参,来定制混合器生成的精确样式。当@include混合器时,参数其实就是可以赋值给css属性值的变量,如下所示:

@mixin when($state) {
  @at-root {
    &.#{$state-prefix + $state} {
      @content;
    }
  }
}

1.4 继承

使用scss的时候,最后一个减少重复的主要特性就是选择器继承,选择器继承是说一个选择器可以继承为另一个选择器定义的所有样式。这个通过@extend语法实现。如下所示:

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

1.5 @content

在引用混合样式的时候,可以先将一段代码导入到混合指令中,然后再输出混合样式,额外导入的部分将出现在@content标志的地方:

@mixin apply-to-ie6-only {
  * html {
    @content;
  }
}
@include apply-to-ie6-only {
  #logo {
    background-image: url(/logo.gif);
  }
}

编译后如下所示:

* html #logo {
  background-image: url(/logo.gif);
}

关于@content的使用,在element-ui的代码中随处可见,如下所示:

@mixin placeholder {
  &::-webkit-input-placeholder {
    @content
  }

  &::-moz-placeholder {
    @content
  }

  &:-ms-input-placeholder {
    @content
  }
}

2. BEM

BEM的意思就是块(block)元素(element)修饰符(modifier)组成,是由Yandex团队提出的一种CSS Class命名方法,旨在更好的创建CSS/Sass模块。BEM命名具有一定的规范,全名规则为模块名 + 元素名 + 修饰器名。如.el-alert--successelement-ui源码中严格遵守了BEM命名规范,并将元素修饰符单独创建了mixin,方便复用代码。


@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;
    }
  }
}

2.1 块

BEM命名规范中,一个块可以当作一个组件,在element-ui中还引入了命名空间,如alert组件的命名为:el-alert。其中el为命名空间,alert为组件名。使用命名空间主要是为了防止组件冲突。

2.2 元素

元素是块的子节点,为了表明某个组件的元素,需要在块名后面加上__element,如element-ui中的alert组件,当需要在组件上显示icon时,可以命名为.el-alert__icon,让人一看就知道是为alert组件中的icon定义的样式。

2.3 修饰符

修饰符是改变某个块的外观的标志。要使用修饰符,可以将 --modifier 添加到块中。如element-ui中的alert组件,可以根据不同的颜色来表示不同的状态,有表示成功错误警告等不同类型的alert,不同的类型只需使用不同的样式既可。如:.el-alert--success.el-alert--error等。

3.定义组件样式

了解完scssBEM后,就来了解下element-ui是如何定义组件样式的。除了date-picker组件外,其它组件基本上都是一个组件对应一个.scss文件。这里主要介绍一下alert组件是如何定义样式的,搞懂了alert组件后,其它的组件样式就很容易看懂了,都大同小异。

3.1 引入公共样式

首先在文件的最开头,引入mixinsvar这两个公共样式。

@import "mixins/mixins";
@import "common/var";

3.2 定义块级元素

定义块级元素,就是BEM规范中的B,首先来看一下b()混合器的代码,如下所示:

@mixin b($block) {
  $B: $namespace+'-'+$block !global;

  .#{$B} {
    @content;
  }
}

该混合器是传递一个block变量名,再根据命名空间来生成一个变量,然后再使用.#{$B}来创建一个class,比如传入一个alert,生成后的类名是这样子的。

.el-alert{
  @content;
}

类型创建好后,就需要发挥@content;的作用了,就是将相关的样式复制进来,如下所示调用b的样式:

@include b(alert) {
  width: 100%;
  padding: $--alert-padding;
  margin: 0;
  box-sizing: border-box;
  border-radius: $--alert-border-radius;
  position: relative;
  background-color: $--color-white;
  overflow: hidden;
  opacity: 1;
  display: flex;
  align-items: center;
  transition: opacity .2s;
}

生成后的代码如下所示:

.el-alert {
  width: 100%;
  padding: $--alert-padding;
  margin: 0;
  box-sizing: border-box;
  border-radius: $--alert-border-radius;
  position: relative;
  background-color: $--color-white;
  overflow: hidden;
  opacity: 1;
  display: flex;
  align-items: center;
  transition: opacity .2s;
}

3.3 定义子级元素

定义子级元素,就是BEM规范中的Ee混合器的代码如下所示:

@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;
      }
    }
  }
}

这里,需要先搞懂eachat-root的用法。

@each的作用是将$element中的元素名遍历出来赋值给$unit变量。然后再生成类似el-alert__xxclass名称。

@at-root指令可以使一个或多个规则被限定输出在文档的根层级上,而不是被嵌套在其父选择器下。

hitAllSpecialNestRule用于判断类型是否为修饰符样式标记样式伪类样式中的一种。如果是其中的一种,需要将其嵌套在&下。如下所示为icon的样式。

@include e(icon) {
    font-size: $--alert-icon-size;
    width: $--alert-icon-size;
    @include when(big) {
      font-size: $--alert-icon-large-size;
      width: $--alert-icon-large-size;
    }
  }

3.4 定义修饰符

定义子级元素,就是BEM规范中的Mm混合器的代码如下所示:

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

  @at-root {
    #{$currentSelector} {
      @content;
    }
  }
}

搞懂e混合器的实现原理后,看m混合器的实现就更简单了,就是先遍历修饰符,然后再生成具有修饰符的class名。alert中定义的success类型如下所示:

@include m(success) {
    &.is-light {
      background-color: $--alert-success-color;
      color: $--color-success;

      .el-alert__description {
        color: $--color-success;
      }
    }

    &.is-dark {
      background-color: $--color-success;
      color: $--color-white;
    }
  }

总结

样式的定义基本上告一段落了,主要是活用mixins中的混合器和BEM规范,平时多写一些公共样式基本就能熟练相关特性了。

上一篇下一篇

猜你喜欢

热点阅读