听说过 BEM 吗?聊聊如何落地 BEM 规范

前言

哈喽大家好!我是 嘟老板 。随着时代的发展,大家对于前端应用的体验和美观要求越来越高,相应的,前端程序中的 CSS 应用也越来越多,越来越复杂。如何合理的组织 CSS 代码,提高代码的可读性和可维护性至关重要。今天我们就聊聊组织 CSS 代码的规范之一- BEM。

阅读本文您将收获:

  1. BEM 规范定义及优势。
  2. 如何在项目中落地 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。

如您对文章内容有任何疑问或想深入讨论,欢迎评论区留下您的问题和见解。

技术简而不凡,创新生生不息。我是 嘟老板,咱们下期再会。


往期推荐

相关推荐
郑州光合科技余经理1 天前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
专业程序开发源1 天前
springboot简历管理系统81389-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·php·课程设计
专业程序开发源1 天前
springboot社区养老系统44071-计算机课程设计、毕业设计
vue.js·spring boot·后端·python·django·php·课程设计
小呆呆6661 天前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
Dovis(誓平步青云)1 天前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan1 天前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_1 天前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒1 天前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技1 天前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
星光开发者1 天前
基于决策树随机森林算法的小说推荐系统的设计与实现-计算机毕设【课程设计】78485
vue.js·算法·微信小程序·django·php·课程设计·express