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

前言

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

阅读本文您将收获:

  1. BEM 规范定义及优势。
  2. 如何在项目中落地 BEM 规范。

什么是 BEM

BEMCSS 类选择器命名规范,可划分为以下三部分:

  • BBlock ,有意义的独立实体,如 menuheadercontainerinput 等。
  • EElementBlock 的一部分,没有独立含义,在语义上和 Block 有关联,如 menu itemheader titlecontainer content 等。
  • MModifierBlockElement 上的标志性描述,通常用于改变外观和行为,如 disabledcheckedhover 等。

其中 BlockElement 之间使用 双下划线 __ 连接;Block/ElementModifier 之间使用 双横线 -- 连接;

例如:

css 复制代码
.block {}
.block__element {}
.block--modifier {}
.block__element--modifier {}

为什么使用 BEM

模块化

应用 BEM 规范,Block 下的所有样式作为一个整体,不会与其他模块产生耦合或交集;

可重用

每个 Block 下的 CSS 都是一个整体,可以在项目内自由使用,或者直接迁移到新项目,不会有代码缺失或遗漏问题。

结构清晰

通过选择器名称,便可以很清晰的知道 Block 下的所有 CSS,明确选择器间的从属关系,了解不同元素之间的关联等。

高度契合预处理框架

常用的 Sass/Lesscss 预处理框架,都支持嵌套定义。

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 各部分的多种组合方式,定义了如下几个函数:

  • buseBem 的参数 block + 后缀 blockSuffix 按规则生成 class 名称。
  • euseBem 的参数 block + 元素名 element 按规则生成 class 名称。
  • museBem 的参数 block + 标志名 modifier 按规则生成 class 名称。
  • beuseBem 的参数 block + 后缀 blockSuffix 和元素名 element 按规则生成 class 名称。
  • bmuseBem 的参数 block + 后缀 blockSuffix 和标志名 modifier 按规则生成 class 名称。
  • emuseBem 的参数 block + 元素名 element 和标志名 modifier 按规则生成 class 名称。
  • bemuseBem 的参数 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 样式块,可内嵌 be 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 代码块,并分别设置样式。当组件属性 disabledtrue 时,按钮文本显示为红色;为 false 时,按钮文本显示为灰色。

在页面中引入 TextButton,看下效果:

html 复制代码
<template>
  <test-button />
  <test-button disabled />
</template>

生成的类名完全符合 BEM 规范。

结语

本文重点介绍了 BEM 规范的相关内容及在 Vue/Sass 项目中的应用方法,旨在帮助同学们加深对于 BEM 规范的应用理解。希望对您有所帮助。相关代码已上传至 GitHub,欢迎 star

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

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


往期推荐

相关推荐
桂月二二43 分钟前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
沈梦研2 小时前
【Vscode】Vscode不能执行vue脚本的原因及解决方法
ide·vue.js·vscode
hunter2062062 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb2 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角2 小时前
CSS 颜色
前端·css
轻口味2 小时前
Vue.js 组件之间的通信模式
vue.js
浪浪山小白兔3 小时前
HTML5 新表单属性详解
前端·html·html5
lee5764 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579654 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me4 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架