日常的工作中,我們無時無刻不在和樣式打交道。沒有樣式的頁面就如同一部電影,被人隨意地在不同地方做了截取。
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范式。
