前言
哈喽大家好!我是 嘟老板 。随着时代的发展,大家对于前端应用的体验和美观要求越来越高,相应的,前端程序中的 CSS 应用也越来越多,越来越复杂。如何合理的组织 CSS 代码,提高代码的可读性和可维护性至关重要。今天我们就聊聊组织 CSS 代码的规范之一- BEM。
阅读本文您将收获:
- BEM 规范定义及优势。
- 如何在项目中落地 BEM 规范。
什么是 BEM
BEM 是 CSS 类选择器命名规范,可划分为以下三部分:
- B :Block ,有意义的独立实体,如 menu 、header 、container 、input 等。
- E :Element ,Block 的一部分,没有独立含义,在语义上和 Block 有关联,如 menu item ,header title ,container content 等。
- M :Modifier ,Block 或 Element 上的标志性描述,通常用于改变外观和行为,如 disabled ,checked ,hover 等。
其中 Block 与 Element 之间使用 双下划线 __ 连接;Block/Element 与 Modifier 之间使用 双横线 -- 连接;
例如:
css
.block {}
.block__element {}
.block--modifier {}
.block__element--modifier {}
为什么使用 BEM
模块化
应用 BEM 规范,Block 下的所有样式作为一个整体,不会与其他模块产生耦合或交集;
可重用
每个 Block 下的 CSS 都是一个整体,可以在项目内自由使用,或者直接迁移到新项目,不会有代码缺失或遗漏问题。
结构清晰
通过选择器名称,便可以很清晰的知道 Block 下的所有 CSS,明确选择器间的从属关系,了解不同元素之间的关联等。
高度契合预处理框架
常用的 Sass/Less 等 css 预处理框架,都支持嵌套定义。
如 Sass:
scss
.parent {
&__child {}
&--disabled {}
}
编译成 css 的结果如下:
css
.parent .parent__child {}
.parent .parent--disabled {}
是不是很契合 BEM,遵守命名规范的同时,减少代码量,又限定了作用域,避免样式污染,简直是一举多得。
落地 BEM
那有的小伙伴就想说了,按照 BEM 规范命名好麻烦呀,类名又臭又长,显得多臃肿!
诚然,要记住规范,还要写超长的类名,确实不够优雅。我们可以封装符合规范的生成工具,既可遵循 BEM,又能减轻心智负担。
下面是基于 Vue3 + Sass 的封装思路,供参考。
useBem composition api
javascript
/**
* 生成 class 类名
*
* 规则:[block]-[blockSuffix]__[element]--[modifier]
* @param block 块名称
* @param blockSuffix 块名称后缀
* @param element 元素名称
* @param modifier 变更标志名称
* @returns
*/
function genBemClass(
block: string,
blockSuffix?: string,
element?: string,
modifier?: string
) {
let clsName = block
if (blockSuffix) clsName += `-${blockSuffix}`
if (element) clsName += `__${element}`
if (modifier) clsName += `--${modifier}`
return clsName
}
export function useBem(block: string) {
const b = (blockSuffix?: string) => {
return genBemClass(block, blockSuffix)
}
const e = (element: string) => {
return genBemClass(block, '', element)
}
const m = (modifier: string) => {
return genBemClass(block, '', modifier)
}
const be = (blockSuffix: string, element: string) => {
return genBemClass(block, blockSuffix, element)
}
const bm = (blockSuffix: string, modifier: string) => {
return genBemClass(block, blockSuffix, '', modifier)
}
const em = (element: string, modifier: string) => {
return genBemClass(block, '', element, modifier)
}
const bem = (blockSuffix: string, element: string, modifier: string) => {
return genBemClass(block, blockSuffix, element, modifier)
}
return {
b,
e,
m,
be,
bm,
em,
bem
}
}
genBemClass 函数定义了 BEM 规范生成类名的规则:
bash
[block]-[blockSuffix]__[element]--[modifier]
其中:
- block :块名称,如 header。
- blockSuffix :块名称后缀,用于定义更语义化的块名称,如 header-content。
- element :元素名称,如 header__title。
- modifier :标志名称, 如 header__title--hover。
useBem 组合 api 根据 BEM 各部分的多种组合方式,定义了如下几个函数:
- b :useBem 的参数 block + 后缀 blockSuffix 按规则生成 class 名称。
- e :useBem 的参数 block + 元素名 element 按规则生成 class 名称。
- m :useBem 的参数 block + 标志名 modifier 按规则生成 class 名称。
- be :useBem 的参数 block + 后缀 blockSuffix 和元素名 element 按规则生成 class 名称。
- bm :useBem 的参数 block + 后缀 blockSuffix 和标志名 modifier 按规则生成 class 名称。
- em :useBem 的参数 block + 元素名 element 和标志名 modifier 按规则生成 class 名称。
- bem :useBem 的参数 block + 后缀 blockSuffix + 元素名 element + 标志名 modifier 按规则生成 class 名称。
vue 组件中,按照如下方式应用即可:
html
<template>
<div :class="ub.b()">
<div :class="ub.b('content')"></div>
<div :class="ub.e('title')"></div>
</div>
</template>
<script setup>
const ub = useBem('header')
</script>
Sass mixin
生成类名称的组合函数搞定了,接下来我们使用 Sass @mixin 来创建类选择生成工具。mixin 允许我们像 js 函数一样传递参数。
sass
// block 与 element 分隔符
$element-separator: '__' !default;
// block/element 与 modifier 分隔符
$modifier-separator: '--' !default;
@mixin b($block) {
.#{$block} {
@content;
}
}
@mixin e($element) {
$selector: &;
$currentSelector: '';
@each $unit in $element {
$currentSelector: #{$currentSelector +
$selector +
$element-separator +
$unit +
','};
}
@at-root {
#{$currentSelector} {
@content;
}
}
}
@mixin m($modifier) {
$selector: &;
$currentSelector: '';
@each $unit in $modifier {
$currentSelector: #{$currentSelector +
$selector +
$modifier-separator +
$unit +
','};
}
@at-root {
#{$currentSelector} {
@content;
}
}
}
定义了以下三个 mixin:
- b :创建 block 样式块。
- e :创建 element 样式块,可内嵌 b mixin 中。
- m :创建 modifier 样式块,可内嵌 b 或 e mixin 中。
可能有的小伙伴不太熟悉 Sass 语法,简单介绍下:
- @each... in... :类似 js 的遍历,in 后面是要遍历的 list ,@each 后是 list 的每个元素,大括号{} 中的代码块会为 list 的每个元素依次执行。
- @content :类似 vue 的插槽,允许我们定义样式块替换 @content,比如:
css
@mixin hover {
&:hover {
@content;
}
}
.button {
border: 1px solid black;
@include hover {
border-width: 2px;
}
}
编译后的 css 如下:
css
.button {
border: 1px solid black;
}
.button:hover {
border-width: 2px;
}
使用 { border-width: 2px; }
样式块替换 @content。
- @at-root:将指定选择器放到文档根路径下,而不是保持定义时的嵌套关系。比如:
scss
.container {
@at-root .btn { background-color: blue; }
}
编译为 css 后的结果为:
css
.container {}
.btn { background-color: blue; }
测试应用
我们来创建个 TestButton 组件应用一下,代码如下:
html
<template>
<button :class="ub.b()">
<span :class="[ub.e('label'), disabled ? ub.em('label', 'disabled') : '']">Test Button</span>
</button>
</template>
<script setup>
import { useBem } from '@/hooks'
const ub = useBem('test-button')
defineProps({
disabled: {
type: Boolean,
default: false
}
})
</script>
<style lang="scss">
@use '@/styles/mixins.scss' as *;
@include b(test-button) {
background: green;
@include e(label) {
color: red;
@include m(disabled) {
color: gray;
}
}
}
</style>
使用 useBem 返回的函数创建相关 class 类名,通过 Vue 语法绑定到相应元素 class 属性上。
引入 Sass mixins ,生成相应的 Sass 代码块,并分别设置样式。当组件属性 disabled 为 true 时,按钮文本显示为红色;为 false 时,按钮文本显示为灰色。
在页面中引入 TextButton,看下效果:
html
<template>
<test-button />
<test-button disabled />
</template>
生成的类名完全符合 BEM 规范。
结语
本文重点介绍了 BEM 规范的相关内容及在 Vue/Sass 项目中的应用方法,旨在帮助同学们加深对于 BEM 规范的应用理解。希望对您有所帮助。相关代码已上传至 GitHub,欢迎 star。
如您对文章内容有任何疑问或想深入讨论,欢迎评论区留下您的问题和见解。
技术简而不凡,创新生生不息。我是 嘟老板,咱们下期再会。