结合Elementplus源码讲解BEM的使用
前言
早就聽說過使用BEM命名管理樣式,平時在項目中也有用到,但總感覺寫起來太麻煩,最近剛好在看Elementplus源碼,發(fā)現(xiàn)Elementplus使用BEM的方案非常棒,本文就來結(jié)合ElementPlus源碼分析一下如何在項目中優(yōu)雅的使用BEM命名
BEM的介紹
BEM是一種針對css的前端命名規(guī)范,**是塊(Block),元素(Element),修飾符(Modifier)**的簡寫。
Block可以理解為模塊,比如:article,dialog,sidebar,form,tab
Element可以理解為塊里的元素,比如form里面的input,submit
modifier可以理解為對block或者element的修飾,比如修飾form__submit–disable,form–theme-dark
BEM的命名規(guī)范
.block { } .block__element { } .block--modifier { } .block__element--modifier { }使用__來連接block和element,使用–來連接block和modifier
清楚了概念和命名規(guī)范,下面我們就來看ElementPlus是怎么實現(xiàn)BEM的
BEM的實踐
<template><div class="dialog-overlay" v-if="visible"><section class="dialog"><header class="dialog__header"><div class="dialog-title">{{ title }}</div><span class="dialog-header__close" @click="visible=false">X</span></header><div class="dialog__body">{{ message }}</div><footer class="dialog__footer"><button class="dialog__btn">{{ cancelBtnTxt }}</button><button class="dialog__btn dialog__btn--primary">{{ comfirmBtnTxt }}</button></footer></section></div> </template> ? <script setup lang="ts"> import { ref } from 'vue' const props = defineProps({title:{type:String,default:'Title'},message:String,cancelBtnTxt:{type:String,default:'Cancel'},comfirmBtnTxt:{type:String,default:'Comfirm'} }) const visible = ref(true) const close = ()=>{visible.value = false } </script> ? <style scoped lang="scss"> :global(body){font-family: PingFang SC,Microsoft YaHei,Arial,sans-serif; } .dialog-overlay{position: fixed;top: 0;right: 0;bottom: 0;left: 0;z-index: 2000;height: 100%;background-color:rgba(0, 0, 0, .5);overflow: auto; } ? .dialog {position: fixed;left: 50%;top: 50%;transform: translate(-50%,-50%);border-radius:2px;background: #fff;width: 559px;min-height: 200px;&__header{padding: 20px 20px 10px;}&__title{color: #333;font-size: 18px;}&__body{padding: 30px 20px;font-size: 14px;color: #333;}&-close{position: absolute;right: 15px;top: 15px;color: #999;font-size: 18px;}&__btn{min-width: 33px;padding: 8px 15px;background: none;border: 1px solid #ddd;border-radius: 2px;color: #666;margin-left: 10px;cursor: pointer;&--primary{background: #409eff;color: #fff;border-color:#409eff;}}&__footer{padding: 10px 20px 20px;display: flex;justify-content: flex-end;} } </style>代碼很簡單,一看就懂,不做過多解釋,下面看用ElementPlus的方法如何進行改造
useNamespace
首先定義一個工具方法useNamespace,此函數(shù)返回了符合BEM命名規(guī)則的方法
// useNamespace.ts import { computed, unref } from 'vue' ? // 定義一個組件的命名前綴 const defaultNamespace = 'poliwen' ? // 定義一個描述組件狀態(tài)的變量 const statePrefix = 'is-' ? // 定義個_bem方法,此方法返回符合BEM規(guī)范的命名 const _bem = ( namespace: string,block: string,blockSuffix: string,element: string,modifier: string, ) => {let cls = `${namespace}-${block}`if (blockSuffix)cls += `-${blockSuffix}` ?if (element)cls += `__${element}` ?if (modifier)cls += `--${modifier}` ?return cls } // useNamespace.ts // 定義一個命名空間,并且返回BEM方法 export const useNamespace = (block: string) => {const namespace = computed(() => defaultNamespace)const b = (blockSuffix = '') =>_bem(unref(namespace), block, blockSuffix, '', '')const e = (element?: string) =>element ? _bem(unref(namespace), block, '', element, '') : ''const m = (modifier?: string) =>modifier ? _bem(unref(namespace), block, '', '', modifier) : ''const be = (blockSuffix?: string, element?: string) =>blockSuffix && element? _bem(unref(namespace), block, blockSuffix, element, ''): ''const em = (element?: string, modifier?: string) =>element && modifier? _bem(unref(namespace), block, '', element, modifier): ''const bm = (blockSuffix?: string, modifier?: string) =>blockSuffix && modifier? _bem(unref(namespace), block, blockSuffix, '', modifier): ''const bem = (blockSuffix?: string, element?: string, modifier?: string) =>blockSuffix && element && modifier? _bem(unref(namespace), block, blockSuffix, element, modifier): ''const is: {(name: string, state: boolean | undefined): string(name: string): string} = (name: string, ...args: [boolean | undefined] | []) => {const state = args.length >= 1 ? args[0]! : truereturn name && state ? `${statePrefix}${name}` : ''}return {namespace,b,e,m,be,em,bm,bem,is,} } ? export type UseNamespaceReturn = ReturnType<typeof useNamespace> ?useNamespace庫的使用
import { useNamespace } from './compo/useNamespace' const bs = useNamespace('dialog') ns.b() // el-dialog ns.b('overlay') // el-dialog-overlay ns.e('header') // el-dialog__header ns.m('theme-dark') // el-dialog--theme-dark ns.be('header','close') // el-dialog-header__close ns.em('footer','small') // el-dialog__footer--small ns.bm('footer','small') // el-dialog-footer--small ns.bem('footer','btn','primary') // el-dialog-footer__btn--primary ns.is('closeable') // is-closeablens.b() 返回結(jié)果為 "el-dialog "
ms.b(‘overlay’)
ns.e(‘header’) 返回結(jié)果為 “el-dialog__header”
ns.m(‘theme-dark’) 返回結(jié)果為 “el-dialog–theme-dark”
ns.be(‘header’,‘close’) 返回結(jié)果為 “el-dialog-header__close” 意思為返回一個block + element
ns.em('foter,‘small’) 返回結(jié)果為 “el-dialog__footer–small” 意思為返回一個element + modifier
ns.bm(‘footer’,‘small’) 返回結(jié)果為 “el-dialog-footer–small” 意思為返回一個block + modifier
ns.bem(‘footer’,‘btn’,‘primary’) 返回結(jié)果為" el-dialog-footer__btn–primary" 意思為返回一個block+element+modifer
ns.is(‘closeable’) 返回is-closeable 通常用來描述組件的狀態(tài),如is-closeable 表示:是否顯示關(guān)閉按鈕
知道了useNamespace庫的用法,下面我們來改造dialog.vue的html模板
改造前的代碼
<template><div class="dialog-overlay" v-if="visible"><section class="dialog"><header class="dialog__header"><div class="dialog__title">{{ title }}</div><span class="dialog-header__close" @click="visible=false">X</span></header><div class="dialog__body">{{ message ?? 'BEM是一種針對css的前端命名規(guī)范,是塊(Block),元素(Element),修飾符(Modifier)的簡寫。' }}</div><footer class="dialog__footer"><button class="dialog__btn">{{ cancelBtnTxt }}</button><button class="dialog__btn dialog__btn--primary">{{ comfirmBtnTxt }}</button></footer></section></div> </template>改造后的代碼
<template><div :class="ns.b('overlay')" v-if="visible"><section :class="ns.b()"><header :class="ns.e('header')"><div :class="ns.e('title')">{{ title }}</div><span :class="ns.be('header','close')" @click="visible=false">X</span></header><div :class="ns.e('body')">{{ message ?? 'BEM是一種針對css的前端命名規(guī)范,是塊(Block),元素(Element),修飾符(Modifier)的簡寫。' }}</div><footer :class="ns.e('footer')"><button :class="ns.e('btn')">{{ cancelBtnTxt }}</button><button :class="[ns.e('btn'), ns.em('btn','primary')]">{{ comfirmBtnTxt }}</button></footer></section></div> </template> ? <script setup lang="ts"> import { ref } from 'vue' import { useNamespace } from '../composables/useNamespace' const ns = useNamespace('dialog') const visible = ref(true) const close = ()=>{visible.value = false } </script>scss也需要改造,新增了一個$nameSpace命名空間
<style scoped lang="scss"> :global(body){font-family: PingFang SC,Microsoft YaHei,Arial,sans-serif; } $namespace: 'poliwen'; .#{$namespace}-dialog-overlay{position: fixed;top: 0;right: 0;bottom: 0;left: 0;z-index: 2000;height: 100%;background-color:rgba(0, 0, 0, .5);overflow: auto; } ? .#{$namespace}-dialog {position: fixed;left: 50%;top: 50%;transform: translate(-50%,-50%);border-radius:2px;background: #fff;width: 559px;min-height: 200px;&__header{padding: 20px 20px 10px;}&__title{color: #333;font-size: 18px;}&__body{padding: 30px 20px;font-size: 14px;color: #333;}&-header{&__close{position: absolute;right: 15px;top: 15px;color: #999;font-size: 18px;}}&__btn{min-width: 33px;padding: 8px 15px;background: none;border: 1px solid #ddd;border-radius: 2px;color: #666;margin-left: 10px;cursor: pointer;&--primary{background: #409eff;color: #fff;border-color:#409eff;}}&__footer{padding: 10px 20px 20px;display: flex;justify-content: flex-end;} } </style>BEM的mixins的封裝
我們還可以將BEM的命名封裝成scss的mixins方法
$namespace: 'poliwen'; $common-separator: '-'; $element-separator: '__'; $modifier-separator: '--'; $state-prefix: 'is-'; $namespace: 'el'; // 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) orcontainPseudoClass($selector); } ? ? // bem('block', 'element', 'modifier') => 't5-block__element--modifier' @function bem($block, $element: '', $modifier: '') {$name: $namespace + $common-separator + $block; ?@if $element != '' {$name: $name + $element-separator + $element;} ?@if $modifier != '' {$name: $name + $modifier-separator + $modifier;} ?// @debug $name;@return $name; } ? // 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 + $selector + $modifier-separator + $unit + ","};} ?@at-root {#{$currentSelector} {@content;}} }以上代碼稍稍有點復(fù)雜,感興趣的可以去學(xué)習(xí)下scss的高級用法
定義好mixins方法之后,我們最終的樣式代碼如下
<style scoped lang="scss"> @import "../assets/mixins.scss"; :global(body){font-family: PingFang SC,Microsoft YaHei,Arial,sans-serif; } @include b(dialog) {&-overlay{position: fixed;top: 0;right: 0;bottom: 0;left: 0;z-index: 2000;height: 100%;background-color:rgba(0, 0, 0, .5);overflow: auto;}position: fixed;left: 50%;top: 50%;transform: translate(-50%,-50%);border-radius:2px;background: #fff;width: 559px;min-height: 200px;@include e(header){padding: 20px 20px 10px;}@include e(title){color: #333;font-size: 18px;}@include e(body){padding: 30px 20px;font-size: 14px;color: #333;}&-header{&__close{position: absolute;right: 15px;top: 15px;color: #999;font-size: 18px;}}@include e(btn){min-width: 33px;padding: 8px 15px;background: none;border: 1px solid #ddd;border-radius: 2px;color: #666;margin-left: 10px;cursor: pointer;&--primary{background: #409eff;color: #fff;border-color:#409eff;}}@include e(footer){padding: 10px 20px 20px;display: flex;justify-content: flex-end;} } </style>總結(jié)
BEM樣式命名是各大主流組件庫的常用命名方法,因此掌握它的使用很有必要,本文主要結(jié)合elementPlus的源碼分析了BEM的妙用,通過定義一個useNamespace工具庫,來生成符合BEM命名規(guī)范的方法,使用scss編寫符合BEM命名的mixins,在組件中結(jié)合useNamespace和minxins就能很方便的使用BEM命名大法。
)
最后
為大家準(zhǔn)備了一個前端資料包。包含54本,2.57G的前端相關(guān)電子書,《前端面試寶典(附答案和解析)》,難點、重點知識視頻教程(全套)。
有需要的小伙伴,可以點擊下方卡片領(lǐng)取,無償分享
總結(jié)
以上是生活随笔為你收集整理的结合Elementplus源码讲解BEM的使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在个人博客网站上添加QQ邮箱的邮我功能
- 下一篇: 【第19天】给定一个整数 n,请你打印出