记录一些我自己在看element-ui源码过程中关于样式部分的理解,如果有不准确的地方,欢迎探讨^_^。
一、样式目录与打包方式
1.1 样式代码目录
element-ui的样式代码位于packages\theme-chalk\src
目录下:\
包含了公用的样式变量定义、混合宏(mixins)、函数、字体文件等和各个组件的scss样式文件。
1.2 样式代码打包
根目录下的package.json中,build:theme
指令是用于打包样式的命令
"build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk"
复制代码
该打包命令有3步:
-
“node build/bin/gen-cssfile”:
用于生成index.scss文件,生成的index.scss文件中通过@import导入了base.scss和各个组件的scss文件,那么index.scss可以作为整个组件库的样式总入口文件。 -
“gulp build –gulpfile packages/theme-chalk/gulpfile.js”:
以packages/theme-chalk/gulpfile.js
为入口执行gulp build
命令;
在gulpfile.js
中,以packages/theme-chalk/src
下的所有scss文件为入口进行编译;
所以既可以编译出整个组件库的样式文件index.css(以index.scss为入口编译出来的),也可以编译出各个组件单独的css样式文件(以各组件自身scss文件为入口)。 -
“cp-cli packages/theme-chalk/lib lib/theme-chalk”:
一个简单的拷贝操作
二、样式变量
element-ui中样式相关的scss变量都在packages\theme-chalk\src\common\var.scss
文件中。
element-ui对样式变量进行了分层,尤其是对于颜色变量的处理。所谓变量分层是指变量分为3个层级:顶层变量 推导变量 组件变量
。
- 顶层变量
包括:品牌的主色、success、warning、danger、info等常用色。如:$--color-primary: #409EFF !default; $--color-white: #FFFFFF !default; $--color-black: #000000 !default; $--color-success: #67C23A !default; $--color-warning: #E6A23C !default; $--color-danger: #F56C6C !default; $--color-info: #909399 !default; 复制代码
- 推导变量
即根据顶层变量,通过一定的运算推导所得到。如:$--color-primary-light-1: mix($--color-white, $--color-primary, 10%) !default; /* 53a8ff */ $--color-primary-light-2: mix($--color-white, $--color-primary, 20%) !default; /* 66b1ff */ $--color-primary-light-3: mix($--color-white, $--color-primary, 30%) !default; /* 79bbff */ $--color-primary-light-4: mix($--color-white, $--color-primary, 40%) !default; /* 8cc5ff */ $--color-primary-light-5: mix($--color-white, $--color-primary, 50%) !default; /* a0cfff */ $--color-primary-light-6: mix($--color-white, $--color-primary, 60%) !default; /* b3d8ff */ $--color-primary-light-7: mix($--color-white, $--color-primary, 70%) !default; /* c6e2ff */ $--color-primary-light-8: mix($--color-white, $--color-primary, 80%) !default; /* d9ecff */ $--color-primary-light-9: mix($--color-white, $--color-primary, 90%) !default; /* ecf5ff */ 复制代码
- 组件变量
即组件样式中直接使用的变量$--button-primary-border-color: $--color-primary !default; 复制代码
样式分层带来的好处有:
- 代码规范,易于维护和更新;
- 友好的主题定制功能,修改顶层变量即可达到换肤的效果。
三、按需引入时的样式导入原理
element-ui提供了按需引入的功能,旨在减少引入的组件文件体积,从而减少打包后的项目文件体积。
我们知道,element-ui的组件vue代码中并没有样式的代码,组件的样式是单独编写并打包成独立的css文件的。那么在按需引入时,是如何引入组件的样式呢。
按需引入时,我们需要使用babel-plugin-component
插件,并配置:
// .babel.rc
{
"presets": [
["es2015", { "modules": false }]
],
"plugins": [["component", [
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]]]
}
复制代码
这是一个babel的插件,其功能其实很简单:在代码编译过程中,当检测到import element-ui的组件时,自动插入一条import 组件的css文件的语句,从而将组件的样式也引入了。
四、使用Sass混合宏(@mixin),快速编写BEM样式代码
BEM是一种CSS命令规范,作用是使css命名更加容易阅读和理解。具体可以参考这边文章:CSS — BEM 命名规范。
在element-ui中,广泛使用了BEM的命名方式,而其样式代码中使用BEM的方式也值得借鉴。
4.1 定义b/m/e混合宏
在packages\theme-chalk\src\mixins\mixins.scss
文件中定义了混合宏b/m/e
/* BEM
-------------------------- */
@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;
}
}
}
复制代码
4.2 业务scss文件中使用b/m/e混合宏
以card.scss的代码片段来看:
// card.scss
@import "mixins/mixins";
@import "common/var";
@include b(card) {
border-radius: $--card-border-radius;
border: 1px solid $--card-border-color;
background-color: $--color-white;
overflow: hidden;
color: $--color-text-primary;
transition: 0.3s;
@include when(always-shadow) {
box-shadow: $--box-shadow-light;
}
@include when(hover-shadow) {
&:hover,
&:focus {
box-shadow: $--box-shadow-light;
}
}
@include e(header) {
padding: #{$--card-padding - 2 $--card-padding};
border-bottom: 1px solid $--card-border-color;
box-sizing: border-box;
}
@include e(body) {
padding: $--card-padding;
}
}
复制代码
编译后的card.css文件:
.el-card {
border-radius: 4px;
border: 1px solid #ebeef5;
background-color: #fff;
overflow: hidden;
color: #303133;
-webkit-transition: 0.3s;
transition: 0.3s;
}
.el-card.is-always-shadow,
.el-card.is-hover-shadow:focus,
.el-card.is-hover-shadow:hover {
-webkit-box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.el-card__header {
padding: 18px 20px;
border-bottom: 1px solid #ebeef5;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.el-card__body {
padding: 20px;
}
复制代码
这样的BEM书写设计后续可以借鉴到自己的项目中。
五、响应式
element-ui中提供了基于媒体查询的响应式布局能力,来看看是如何实现的
5.1 响应式断点变量
packages\theme-chalk\src\common\var.scss
文件中定义了关于媒体查询的响应式断点变量
/* Break-point
--------------------------*/
$--sm: 768px !default;
$--md: 992px !default;
$--lg: 1200px !default;
$--xl: 1920px !default;
// maps类型变量
$--breakpoints: (
'xs' : (max-width: $--sm - 1),
'sm' : (min-width: $--sm),
'md' : (min-width: $--md),
'lg' : (min-width: $--lg),
'xl' : (min-width: $--xl)
);
复制代码
其中的$--breakpoints
是maps类型变量
5.2 定义res混合宏
packages\theme-chalk\src\mixins\mixins.scss
文件中定义了混合宏res
/* Break-points
-------------------------- */
@mixin res($key, $map: $--breakpoints) {
// 循环断点Map,如果存在则返回
@if map-has-key($map, $key) {
@media only screen and #{inspect(map-get($map, $key))} {
@content;
}
} @else {
@warn "Undefeined points: `#{$map}`";
}
}
复制代码
代码中map-has-key map-get inspect
是scss内置的函数
5.3 使用res编写响应式样式
@include res(xs) {
.el-col-xs-0 {
display: none;
}
@for $i from 0 through 24 {
.el-col-xs-#{$i} {
width: (1 / 24 * $i * 100) * 1%;
}
.el-col-xs-offset-#{$i} {
margin-left: (1 / 24 * $i * 100) * 1%;
}
.el-col-xs-pull-#{$i} {
position: relative;
right: (1 / 24 * $i * 100) * 1%;
}
.el-col-xs-push-#{$i} {
position: relative;
left: (1 / 24 * $i * 100) * 1%;
}
}
}
@include res(sm) {
// 类似代码,省略
}
@include res(md) {
// 类似代码,省略
}
@include res(lg) {
// 类似代码,省略
}
@include res(xl) {
// 类似代码,省略
}
复制代码
编译出来的col.css(代码太多,就展示图片吧)