前端bem Css 规范文档
<h1>BEM 前端规范</h1>
<p>BEM 是 CSS 类选择器命名规范,可划分为以下三部分:</p>
<ul>
<li><strong>B</strong>:Block,有意义的独立实体,如 menu、header、container、input 等。</li>
<li><strong>E</strong>:Element,Block 的一部分,没有独立含义,在语义上和 Block 有关联,如 menu item,header title,container content 等。</li>
<li><strong>M</strong>:Modifier,Block 或 Element 上的标志性描述,通常用于改变外观和行为,如 disabled,checked,hover 等。
其中 Block 与 Element 之间使用 双下划线 __ 连接;Block/Element 与 Modifier 之间使用 双横线 -- 连接;</li>
</ul>
<p>例如:</p>
<pre><code class="language-css">.block {} // 块
.block__element {} // 元素
.block--modifier {} // 块-描述
.block__element--modifier {} // 块-元素-描述</code></pre>
<h2>为什么使用 BEM</h2>
<h4>模块化</h4>
<p>应用 BEM 规范,Block 下的所有样式作为一个整体,不会与其他模块产生耦合或交集;</p>
<h4>可重用</h4>
<p>每个 Block 下的 CSS 都是一个整体,可以在项目内自由使用,或者直接迁移到新项目,不会有代码缺失或遗漏问题。</p>
<h4>结构清晰</h4>
<p>通过选择器名称,便可以很清晰的知道 Block 下的所有 CSS,明确选择器间的从属关系,了解不同元素之间的关联等。</p>
<h4>高度契合预处理框架</h4>
<p>常用的 Sass/Less 等 css 预处理框架,都支持嵌套定义。
如 Sass:</p>
<pre><code class="language-css">.parent {
&amp;__child {}
&amp;--disabled {}
}</code></pre>
<p>编译成 css 的结果如下:</p>
<pre><code class="language-css">.parent .parent__child {}
.parent .parent--disabled {}</code></pre>
<h2>如何使用BEM</h2>
<p>诚然,要记住规范,还要写超长的类名,确实不够优雅。我们可以封装符合规范的生成工具,既可遵循 BEM,又能减轻心智负担。
下面是基于 Vue3 + Sass 的封装思路,供参考。</p>
<p><strong>useBem composition api</strong></p>
<pre><code class="language-javascript">/**
* 生成 class 类名
*
* 规则:[block]-[blockSuffix]__[element]--[modifier]
* @param block 块名称
* @param blockSuffix 块名称后缀
* @param element 元素名称
* @param modifier 变更标志名称
* @returns
*/
function genBemClass(
block,
blockSuffix,
element,
modifier
) {
let clsName = block
if (blockSuffix) clsName += `-${blockSuffix}`
if (element) clsName += `__${element}`
if (modifier) clsName += `--${modifier}`
return clsName
}
/*
* 使用bem规范
*
* @param block 块名
* @returns
*/
export function useBem(block) {
const b = (blockSuffix) =&gt; {
return genBemClass(block, blockSuffix)
}
const e = (element) =&gt; {
return genBemClass(block, &#039;&#039;, element)
}
const m = (modifier) =&gt; {
return genBemClass(block, &#039;&#039;, modifier)
}
const be = (blockSuffix, element) =&gt; {
return genBemClass(block, blockSuffix, element)
}
const bm = (blockSuffix, modifier) =&gt; {
return genBemClass(block, blockSuffix, &#039;&#039;, modifier)
}
const em = (element, modifier) =&gt; {
return genBemClass(block, &#039;&#039;, element, modifier)
}
const bem = (blockSuffix, element, modifier) =&gt; {
return genBemClass(block, blockSuffix, element, modifier)
}
return {
b,
e,
m,
be,
bm,
em,
bem
}
}
</code></pre>
<p><code>genBemClass</code> 函数定义了 BEM 规范生成类名的规则:</p>
<pre><code class="language-css">[block]-[blockSuffix]__[element]--[modifier]</code></pre>
<p>其中:</p>
<p><strong>block</strong>:块名称,如 header。
<strong>blockSuffix</strong>:块名称后缀,用于定义更语义化的块名称,如 header-content。
<strong>element</strong>:元素名称,如 header<strong>title。
<strong>modifier</strong>:标志名称, 如 header</strong>title--hover。</p>
<p>useBem 组合 api 根据 BEM 各部分的多种组合方式,定义了如下几个函数:</p>
<ul>
<li><strong>b</strong>:useBem 的参数 block + 后缀 blockSuffix 按规则生成 class 名称。</li>
<li><strong>e</strong>:useBem 的参数 block + 元素名 element 按规则生成 class 名称。</li>
<li><strong>m</strong>:useBem 的参数 block + 标志名 modifier 按规则生成 class 名称。</li>
<li><strong>be</strong>:useBem 的参数 block + 后缀 blockSuffix 和元素名 element 按规则生成 class 名称。</li>
<li><strong>bm</strong>:useBem 的参数 block + 后缀 blockSuffix 和标志名 modifier 按规则生成 class 名称。</li>
<li><strong>em</strong>:useBem 的参数 block + 元素名 element 和标志名 modifier 按规则生成 class 名称。</li>
<li><strong>bem</strong>:useBem 的参数 block + 后缀 blockSuffix + 元素名 element + 标志名 modifier 按规则生成 class 名称。</li>
</ul>
<p>vue 组件中,按照如下方式应用即可:</p>
<pre><code class="language-javascript">&lt;template&gt;
&lt;div :class=&quot;ub.b()&quot;&gt;
&lt;div :class=&quot;ub.b(&#039;content&#039;)&quot;&gt;&lt;/div&gt;
&lt;div :class=&quot;ub.e(&#039;title&#039;)&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/template&gt;
&lt;script setup&gt;
const ub = useBem(&#039;header&#039;)
&lt;/script&gt;</code></pre>
<h3>Sass mixin</h3>
<p>生成类名称的组合函数搞定了,接下来我们使用 Sass @mixin 来创建类选择生成工具。mixin 允许我们像 js 函数一样传递参数。</p>
<pre><code class="language-css">// block 与 element 分隔符
$element-separator: &#039;__&#039; !default;
// block/element 与 modifier 分隔符
$modifier-separator: &#039;--&#039; !default;
@mixin b($block) {
.#{$block} {
@content;
}
}
@mixin e($element) {
$selector: &amp;;
$currentSelector: &#039;&#039;;
@each $unit in $element {
$currentSelector: #{$currentSelector +
$selector +
$element-separator +
$unit +
&#039;,&#039;};
}
@at-root {
#{$currentSelector} {
@content;
}
}
}
@mixin m($modifier) {
$selector: &amp;;
$currentSelector: &#039;&#039;;
@each $unit in $modifier {
$currentSelector: #{$currentSelector +
$selector +
$modifier-separator +
$unit +
&#039;,&#039;};
}
@at-root {
#{$currentSelector} {
@content;
}
}
}
</code></pre>
<p>定义了以下三个 mixin:</p>
<p>b:创建 block 样式块。
e:创建 element 样式块,可内嵌 b mixin 中。
m:创建 modifier 样式块,可内嵌 b 或 e mixin 中。</p>
<p>可能有的小伙伴不太熟悉 Sass 语法,简单介绍下:</p>
<p>@each... in...:类似 js 的遍历,in 后面是要遍历的 list,@each 后是 list 的每个元素,大括号{} 中的代码块会为 list 的每个元素依次执行。
@content:类似 vue 的插槽,允许我们定义样式块替换 @content,比如:</p>
<pre><code class="language-css">@mixin hover {
&amp;:hover {
@content;
}
}
.button {
border: 1px solid black;
@include hover {
border-width: 2px;
}
}
</code></pre>
<p>编译后的 css 如下:</p>
<pre><code class="language-css">.button {
border: 1px solid black;
}
.button:hover {
border-width: 2px;
}
</code></pre>
<p><strong>使用 { border-width: 2px; } 样式块替换 @content。</strong></p>
<p><strong>@at-root</strong>:将指定选择器放到文档根路径下,而不是保持定义时的嵌套关系。比如:</p>
<pre><code class="language-css">.container {
@at-root .btn { background-color: blue; }
}
</code></pre>
<p>编译为 css 后的结果为:</p>
<pre><code class="language-css">.container {}
.btn { background-color: blue; }
</code></pre>
<h3>测试应用</h3>
<p>我们来创建个 TestButton 组件应用一下,代码如下:</p>
<pre><code class="language-javascript">&lt;template&gt;
&lt;button :class=&quot;ub.b()&quot;&gt;
&lt;span :class=&quot;[ub.e(&#039;label&#039;), disabled ? ub.em(&#039;label&#039;, &#039;disabled&#039;) : &#039;&#039;]&quot;&gt;Test Button&lt;/span&gt;
&lt;/button&gt;
&lt;/template&gt;
&lt;script setup&gt;
import { useBem } from &#039;@/hooks&#039; // 引入bem函数
const ub = useBem(&#039;test-button&#039;)
defineProps({
disabled: {
type: Boolean,
default: false
}
})
&lt;/script&gt;
&lt;style lang=&quot;scss&quot;&gt;
@use &#039;@/styles/mixins.scss&#039; as *; // 这里的mixins.scss 就是我们上面写的scss函数
@include b(test-button) {
background: green;
@include e(label) {
color: red;
@include m(disabled) {
color: gray;
}
}
}
&lt;/style&gt;
</code></pre>
<p>使用 useBem 返回的函数创建相关 class 类名,通过 Vue 语法绑定到相应元素 class 属性上。
引入 Sass mixins,生成相应的 Sass 代码块,并分别设置样式。当组件属性 disabled 为 true 时,按钮文本显示为红色;为 false 时,按钮文本显示为灰色。
在页面中引入 TextButton,看下效果:</p>
<pre><code class="language-javascript">&lt;template&gt;
&lt;test-button /&gt;
&lt;test-button disabled /&gt;
&lt;/template&gt;
</code></pre>
<p>生成的类名完全符合 BEM 规范。</p>