导读
首先来看一个bem命名示例
.my-message-box{}
.my-message-box__header{}
.my-message-box__header--active{}
如果使用已经封装好的bem方法的话,那么可以写成
@include b('message-box') {
border: 1px solid black;
@include e('header') {
color: gray;
@include m('active') {
color: red;
}
}
}
接下来我们来看一下bem方法是如何实现的
sass语法回顾
- !global 变量提升,将局部变量提升为全局变量,在其他函数体内也能访问到此变量
- @at-root将父级选择器直接暴力的改成根选择器
- #{}插值,可以通过#{}插值语法在选择器和属性名中使用SassScript变量
bem方法解析
首先根目录下建目录theme/BEM,BEM下创建三个文件config.scss,function.scss,mixin.scss
config.scss
$namespace: 'my';
$element-separator: '__';
$modifier-separator: '--';
$state-prefix: 'is-';
里面定义了几个变量
function.scss
@import './config';
/* BEM support functions
----------------------- */
@function selectorToString($selector) {
$__selector: inspect($selector);
$_selector: str-slice($__selector, 2, -2);
@return $_selector;
}
@function containsModifier($selector) {
$selector: selectorToString($selector);
@if str-index($selector, $modifier-separator) {
@return true;
} @else {
@return false;
}
}
@function containWhenFlag($selector) {
$selector: selectorToString($selector);
@if str-index($selector, '.' + $state-prefix) {
@return true;
} @else {
@return false;
}
}
@function containPseudoClass($selector) {
$selector: selectorToString($selector);
@if str-index($selector, ':') {
@return true;
} @else {
@return false;
}
}
@function hitAllSpecialNestRule($selector) {
@return containsModifier($selector) or containWhenFlag($selector) or
containPseudoClass($selector);
}
里面定义了几个方法
- containsModifier 方法是判断父级选择器是否包含’–‘
- containWhenFlag 方法是判断 父级选择器是否包含’.is-‘
- containPseudoClass 方法是判断 父级是否包含 ‘:’
理解这几个函数只需要弄清楚自定义函数selectorToString以及inspect和str-slice、str-index这三个sass内建函数 (就是sass自带函数)即可
- 使用inspect(value)使用inspect( value)函数来生成一个对调试Map有用的输出字符串,因为Map无法转换为纯CSS。使用一个作为CSS函数的变量或参数的值将导致错误
- str-slice(string,start-at, end−at:−1)从string 中截取子字符串,通过start−at和end-at 设置始末位置,未指定结束索引值则默认截取到字符串末尾
- str-index(string,substring) 返回一个下标,标示 substring在string 中的起始位置。没有找到的话,则返回 null 值。这里$string必须为字符串
mixin.scss
@import './function';
/* BEM
--------------------- */
@mixin b($block-name) {
$B: $namespace + '-' + $block-name !global;
.#{$B} {
@content;
}
}
@mixin e($element-name) {
$E: $element-name !global;
$selector: &;
$currentSelector: '';
@each $unit in $element-name {
$currentSelector: #{$currentSelector+'.'+$B+$element-separator+$unit + ','};
}
@if hitAllSpecialNestRule($selector) {
@at-root {
#{$selector} {
#{$currentSelector} {
@content;
}
}
}
}
@else {
@at-root {
#{$currentSelector} {
@content;
}
}
}
}
@mixin m($modifier-name) {
$selector: &;
$currentSelector: '';
@each $unit in $modifier-name {
$currentSelector: #{$currentSelector+&+$modifier-separator+$unit+','};
}
@at-root {
#{$currentSelector} {
@content;
}
}
}
@mixin when($state) {
@at-root {
&.#{$state-prefix + $state} {
@content;
}
}
}
@mixin pseudo($pseudo) {
@at-root #{&}#{':#{$pseudo}'} {
@content;
}
}
b方法
- 使用变量的拼接运算;namespace在config.scss文件中有定义,为“el”,假设namespace 在config.scss文件中有定义,为“el”,假设namespace在config.scss文件中有定义,为“el”,假设block为card,则拼接结果为el-card
- !global 变量提升为全局变量
- 使用插值语法将$B这个选择器作为选择器使用
- @content 将引用混合后大括号外额外的样式
e方法
e方法调用了hitAllSpecialNestRule方法(判断父级选择器是否包含'--','is',':')
m方法
m方法流程和b一致,区别在拼接currentSelector字符串时,使用了父级选择器
全局引入
在vue.config.js中配置css如下即可
css: {
loaderOptions: {
sass: {
prependData: "@import '@/theme/BEM/minxin.scss';",
},
},
},
代码里面就可以调用了
<template>
<div class="home">
<div class="my-message-box">
<span class="my-message-box__header">gray</span>
<span class="my-message-box__header--active">red</span>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
@Component({
components: {
HelloWorld,
},
})
export default class Home extends Vue {}
</script>
<style lang="scss">
@include b('message-box') {
border: 1px solid black;
@include e('header') {
color: gray;
@include m('active') {
color: red;
}
}
}
</style>