日常问题记录


前端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;amp;__child {} &amp;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) =&amp;gt; { return genBemClass(block, blockSuffix) } const e = (element) =&amp;gt; { return genBemClass(block, &amp;#039;&amp;#039;, element) } const m = (modifier) =&amp;gt; { return genBemClass(block, &amp;#039;&amp;#039;, modifier) } const be = (blockSuffix, element) =&amp;gt; { return genBemClass(block, blockSuffix, element) } const bm = (blockSuffix, modifier) =&amp;gt; { return genBemClass(block, blockSuffix, &amp;#039;&amp;#039;, modifier) } const em = (element, modifier) =&amp;gt; { return genBemClass(block, &amp;#039;&amp;#039;, element, modifier) } const bem = (blockSuffix, element, modifier) =&amp;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">&amp;lt;template&amp;gt; &amp;lt;div :class=&amp;quot;ub.b()&amp;quot;&amp;gt; &amp;lt;div :class=&amp;quot;ub.b(&amp;#039;content&amp;#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;div :class=&amp;quot;ub.e(&amp;#039;title&amp;#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;/div&amp;gt; &amp;lt;/template&amp;gt; &amp;lt;script setup&amp;gt; const ub = useBem(&amp;#039;header&amp;#039;) &amp;lt;/script&amp;gt;</code></pre> <h3>Sass mixin</h3> <p>生成类名称的组合函数搞定了,接下来我们使用 Sass @mixin 来创建类选择生成工具。mixin 允许我们像 js 函数一样传递参数。</p> <pre><code class="language-css">// block 与 element 分隔符 $element-separator: &amp;#039;__&amp;#039; !default; // block/element 与 modifier 分隔符 $modifier-separator: &amp;#039;--&amp;#039; !default; @mixin b($block) { .#{$block} { @content; } } @mixin e($element) { $selector: &amp;amp;; $currentSelector: &amp;#039;&amp;#039;; @each $unit in $element { $currentSelector: #{$currentSelector + $selector + $element-separator + $unit + &amp;#039;,&amp;#039;}; } @at-root { #{$currentSelector} { @content; } } } @mixin m($modifier) { $selector: &amp;amp;; $currentSelector: &amp;#039;&amp;#039;; @each $unit in $modifier { $currentSelector: #{$currentSelector + $selector + $modifier-separator + $unit + &amp;#039;,&amp;#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;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">&amp;lt;template&amp;gt; &amp;lt;button :class=&amp;quot;ub.b()&amp;quot;&amp;gt; &amp;lt;span :class=&amp;quot;[ub.e(&amp;#039;label&amp;#039;), disabled ? ub.em(&amp;#039;label&amp;#039;, &amp;#039;disabled&amp;#039;) : &amp;#039;&amp;#039;]&amp;quot;&amp;gt;Test Button&amp;lt;/span&amp;gt; &amp;lt;/button&amp;gt; &amp;lt;/template&amp;gt; &amp;lt;script setup&amp;gt; import { useBem } from &amp;#039;@/hooks&amp;#039; // 引入bem函数 const ub = useBem(&amp;#039;test-button&amp;#039;) defineProps({ disabled: { type: Boolean, default: false } }) &amp;lt;/script&amp;gt; &amp;lt;style lang=&amp;quot;scss&amp;quot;&amp;gt; @use &amp;#039;@/styles/mixins.scss&amp;#039; as *; // 这里的mixins.scss 就是我们上面写的scss函数 @include b(test-button) { background: green; @include e(label) { color: red; @include m(disabled) { color: gray; } } } &amp;lt;/style&amp;gt; </code></pre> <p>使用 useBem 返回的函数创建相关 class 类名,通过 Vue 语法绑定到相应元素 class 属性上。 引入 Sass mixins,生成相应的 Sass 代码块,并分别设置样式。当组件属性 disabled 为 true 时,按钮文本显示为红色;为 false 时,按钮文本显示为灰色。 在页面中引入 TextButton,看下效果:</p> <pre><code class="language-javascript">&amp;lt;template&amp;gt; &amp;lt;test-button /&amp;gt; &amp;lt;test-button disabled /&amp;gt; &amp;lt;/template&amp;gt; </code></pre> <p>生成的类名完全符合 BEM 规范。</p>

页面列表

ITEM_HTML