日常的工作中,我们无时无刻不在和样式打交道。没有样式的页面就如同一部电影,被人随意地在不同地方做了截取。
BEM规范应该是对于我们现在前端组件开发中我觉得是最合适的一套范式了。所以,我在自己的日常工作中也是十分的推崇这样的一套CSS范式。
而自己最近也在看各种ui框架的源码,觉得ele对于这块还是处理的蛮好的,所以拿出来说说。
1.BEM
BEM范式我在以前自己的文章中简单的说过,就不再赘述了。
而我们来看看饿了么在BEM这块有着怎样的实践。
//element-ui
//config.scss
$namespace: 'el';
$element-separator: '__';
$modifier-separator: '--';
$state-prefix: 'is-';
element在config.scss里面定义了一些基础的配置项目,主要包括四个部分:
1.整套样式的命名空间,命名空间可以带来不用系统样式的隔离,当然缺点就是我们的样式一定是带有一个namespace的前缀出现
2.B和E之间的连接符
3.E和M之间的连接符
4.状态的前缀 ,因为有很多的用户行为而带来的激活这样的效果。在js中我们会说到is和as。一个是类型的判定,一个是类型的模糊,这是多态的特性体现。所以,同理的话,is在css中代表的就是当前元素状态的判定,例如:is-checked(是否被选中)之类等等
2.“B”的定义
@mixin b($block) { $B: $namespace+'-'+$block !global; .#{$B} { @content; } }
ele通过宏b来实现的BEM中B的定义
这里的话,我通过radio来作为假设。最后我们在b中通过!global提升了一个$B:el-radio。这也是我以前提到过的改良后的BEM。通过插值语句#{ }生成了 “.el-radio”。然后通过@content向生成的B中导入内容。
导入的内容就是通过调用宏b生成的所有的样式。
下面是所有通过mixin b可以生成的样式
//通过宏b生成的所有样式 @include b(radio) { color: $--radio-color; font-weight: $--radio-font-weight; line-height: 1; position: relative; cursor: pointer; display: inline-block; white-space: nowrap; outline: none; font-size: $--font-size-base; @include utils-user-select(none); @include when(bordered) { padding: $--radio-bordered-padding; border-radius: $--border-radius-base; border: $--border-base; box-sizing: border-box; height: $--radio-bordered-height; &.is-checked { border-color: $--color-primary; } &.is-disabled { cursor: not-allowed; border-color: $--border-color-lighter; } & + .el-radio.is-bordered { margin-left: 10px; } } @include m(medium) { &.is-bordered { padding: $--radio-bordered-medium-padding; border-radius: $--button-medium-border-radius; height: $--radio-bordered-medium-height; .el-radio__label { font-size: $--button-medium-font-size; } .el-radio__inner { height: $--radio-bordered-medium-input-height; width: $--radio-bordered-medium-input-width; } } } @include m(small) { &.is-bordered { padding: $--radio-bordered-small-padding; border-radius: $--button-small-border-radius; height: $--radio-bordered-small-height; .el-radio__label { font-size: $--button-small-font-size; } .el-radio__inner { height: $--radio-bordered-small-input-height; width: $--radio-bordered-small-input-width; } } } @include m(mini) { &.is-bordered { padding: $--radio-bordered-mini-padding; border-radius: $--button-mini-border-radius; height: $--radio-bordered-mini-height; .el-radio__label { font-size: $--button-mini-font-size; } .el-radio__inner { height: $--radio-bordered-mini-input-height; width: $--radio-bordered-mini-input-width; } } } & + .el-radio { margin-left: 30px; } @include e(input) { white-space: nowrap; cursor: pointer; outline: none; display: inline-block; line-height: 1; position: relative; vertical-align: middle; @include when(disabled) { .el-radio__inner { background-color: $--radio-disabled-input-fill; border-color: $--radio-disabled-input-border-color; cursor: not-allowed; &::after { cursor: not-allowed; background-color: $--radio-disabled-icon-color; } & + .el-radio__label { cursor: not-allowed; } } &.is-checked { .el-radio__inner { background-color: $--radio-disabled-checked-input-fill; border-color: $--radio-disabled-checked-input-border-color; &::after { background-color: $--radio-disabled-checked-icon-color; } } } & + span.el-radio__label { color: $--color-text-placeholder; cursor: not-allowed; } } @include when(checked) { .el-radio__inner { border-color: $--radio-checked-input-border-color; background: $--radio-checked-icon-color; &::after { transform: translate(-50%, -50%) scale(1); } } & + .el-radio__label { color: $--radio-checked-text-color; } } @include when(focus) { .el-radio__inner { border-color: $--radio-input-border-color-hover; } } } @include e(inner) { border: $--radio-input-border; border-radius: $--radio-input-border-radius; width: $--radio-input-width; height: $--radio-input-height; background-color: $--radio-input-fill; position: relative; cursor: pointer; display: inline-block; box-sizing: border-box; &:hover { border-color: $--radio-input-border-color-hover; } &::after { width: 4px; height: 4px; border-radius: $--radio-input-border-radius; background-color: $--color-white; content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(0); transition: transform .15s cubic-bezier(.71,-.46,.88,.6); } } @include e(original) { opacity: 0; outline: none; position: absolute; z-index: -1; top: 0; left: 0; right: 0; bottom: 0; margin: 0; } &:focus:not(.is-focus):not(:active){ /*获得焦点时 样式提醒*/ .el-radio__inner { box-shadow: 0 0 2px 2px $--radio-input-border-color-hover; } } @include e(label) { font-size: $--radio-font-size; padding-left: 10px; } }
3.“E”的定义
完成了B以后的话,就要处理E了。不过在e中多做了两件事
1.通过each完成了"BE"的样式的生成,例如是input,那么$currentSelector就是".el-radio + __ + input"
2.通过函数处理@return containsModifier($selector) or containWhenFlag($selector) or containPseudoClass($selector) 三种情况
@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; } } } }
4.“M”的定义
最后是m的生成,基本上原理都和前面说到的是一样的了。
例如:el-radio--medium。用来描述radio的size属性。那么$currentSelector就是".el-radio + -- + medium"
@mixin m($modifier) { $selector: &; $currentSelector: ""; @each $unit in $modifier { $currentSelector: #{$currentSelector + & + $modifier-separator + $unit + ","}; } @at-root { #{$currentSelector} { @content; } } }
5.小结
我在这也就是抛砖引玉的说下,精彩的内容还是要大家自己去源码里看,或者自己去试着写一下那就是最好了。
试着写一个vue或者react的组件用上BEM范式去管理类名,肯定也会和我一样,觉得在基于组件化开发的前端项目中,BEM范式绝对是我们管理css的一把利器。
当然,在以后的文章中我也会来说说OO和SMA范式。