巧妙的封装 BEM
el 通过mixin 实现了 BEM (命名空间+模块 + 元素名 + 修饰器名) namespace-block__element--modifier
// 使用方式
.test-bem {
color: purple;
@include b(heade1r) {
color:orange;
@include e((aa,bb,cc)) {
color:black;
@include m (success) {
background: green;
}
}
}
}
// 编译后的结果
.test-bem {
color: purple;
}
.test-bem .el-heade1r {
color: orange;
}
.el-heade1r__aa, .el-heade1r__bb, .el-heade1r__cc {
color: black;
}
.el-heade1r__aa, .el-heade1r__bb, .el-heade1r__cc--success {
background: green;
}
BEM 的封装
- 定义配置相关的变量
$namespace: 'el'; // 命名空间
$element-separator: '__'; // 元素分隔符
$modifier-separator: '--'; // 修饰器分隔符
- 实现 block
// block
@mixin b ($block) {
// !global 将局部变量转为全局变量
$B: $namespace + '-' + $block !global;
// 模板语法
.#{$B} {
@content;
}
}
// 示例
.test-bem {
color: purple;
@include b(heade1r) {
color:orange;
}
}
// 编译后
.test-bem {
color: purple;
}
.test-bem .el-heade1r {
color: orange;
}
-
实现 e mixin:
如果 包含修饰器 状态 或者 伪类 创建一个 与父类同级的 以 $element-separator + element 结尾的类
$element 可以是单个参数 或者 list
该 mixin 依赖与 block mixin 创建的 $B
@mixin e($element) {
$E: $element !global;
$selector: &; // & 表示父类选择器
$currentSelector: "";
@each $unit in $element {
$currentSelector: #{$currentSelector + '.' + $B + $element-separator + $unit + ','}
}
@if hitAllSpecialNestRule($selector) {
// 如果 包含修饰器 状态 或者 伪类 则
// 返回 与父类同级
@at-root {
#{$selector} {
#{$currentSelector} {
@content;
}
}
}
} @else {
@at-root {
#{$currentSelector} {
@content;
}
}
}
}
// 判断是否存在特殊嵌套 包含修饰器,包含状态 包含伪类
@function hitAllSpecialNestRule($selector) {
@return containsModifier($selector) or containWhenFlag($selector) or containPseudoClass($selector);
}
@function selectorToString($selector) {
// inspect($value) 将列表转换成字符串
$selector: inspect($selector);
// str-slice($string, $start-at, $end-at) 截取字符串
$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
}
}
示例代码
// 示例1
.test-bem{
color: purple;
@include b(heade1r) {
color:orange;
}
@include e((aa,bb,cc)) {
color:black;
}
}
// 编译结果
.test-bem {
color: purple;
}
.test-bem .el-heade1r {
color: orange;
}
.el-heade1r__aa, .el-heade1r__bb, .el-heade1r__cc {
color: black;
}
// 示例2
.test-bem--modify{
color: purple;
@include b(heade1r) {
color:orange;
}
@include e((aa,bb,cc)) {
color:black;
}
}
// 编译结果
.test-bem--modify {
color: purple;
}
.test-bem--modify .el-heade1r {
color: orange;
}
.test-bem--modify .el-heade1r__aa, .test-bem--modify .el-heade1r__bb, .test-bem--modify .el-heade1r__cc {
color: black;
}
- modifier 实现
@mixin m ($modifier) {
$selector: &;
$currentSelector: "";
@each $unit in $modifier {
$currentSelector: #{$currentSelector + & + $modifier-separator + $unit + ","};
}
@at-root {
#{$currentSelector} {
@content;
}
}
}
示例代码
.test-bem{
color: purple;
@include b(heade1r) {
color:orange;
}
@include e(aa) {
color:black;
@include m (success) {
background: green;
}
}
}
// 编译后
.test-bem {
color: purple;
}
.test-bem .kk-heade1r {
color: orange;
}
.kk-heade1r__aa {
color: black;
}
.kk-heade1r__aa--success {
background: green;
}
CSS 变量封装
CSS 变量有两种形式:
1. 全局变量
:root { // 全局作用域
// Background --el-bg-color-#{$type}
@include set-component-css-var('bg-color', $bg-color);
@include set-component-css-var('border-color', $border-color);
// --el-text-color-#{$type}
@include set-component-css-var('text-color', $text-color);
// Fill --el-fill-color-#{$type}
@include set-component-css-var('fill-color', $fill-color);
// Border
@include set-css-var-value('border-width', $border-width);
@include set-css-var-value('border-style', $border-style);
@include set-css-var-value('border-color-hover', $border-color-hover);
@include set-css-var-value(
'border',
#{var(--kk-border-width) var(--kk-border-style) var(--kk-border-color)}
);
}
其中有两个 mixin set-component-css-var和set-css--var-value
@include set-component-css-var('bg-color', $bg-color); 示例:
$bg-color: () !default;
$bg-color: map.merge(
(
'': #ffffff,
'page': #ffffff,
'overlay': #ffffff,
),
$bg-color
);
$namespace: "el";
@include set-component-css-var('bg-color', $bg-color);
// 接收两个参数,name 和 $variables 列表
// --el-bg-color: #ffffff
// --el-bg-color-page: #ffffff
// --el-bg-color-overlay: #ffffff
@mixin set-component-css-var($name, $variables) {
@each $attribute, $value in $variables {
#{getCssVarName($name, $attribute)}: #{$value}
}
}
// getCssVarName('bg-color', '') => '--el-bg-color'
@function getCssVarName($arg...) {
@return joinVarName($arg)
}
// 当列表中的元素不为 '' 的时候将其使用 - 连接
@function joinVarName ($list) {
$name: '--' + $namespace;
@each $item in $list {
@if $item != '' {
$name: $name + '-' + $item;
}
}
@return $name;
}
通过遍历列表的方式,实现了多个全局变量。
set-css-var-value 示例:
$namespace: "el";
$border-width: 1px !default;
@include set-css-var-value('border-width', $border-width);
// border-width
// return --el-border-width: 1px
@mixin set-css-var-value($name, $value) {
#{joinVarName($name)}: #{$value};
}
// 当列表中的元素不为 '' 的时候将其使用 - 连接
@function joinVarName ($list) {
$name: '--' + $namespace;
@each $item in $list {
@if $item != '' {
$name: $name + '-' + $item;
}
}
@return $name;
}
2. 局部变量
局部变量的定义 就在当前的组件内部:
$button: () !default;
// map.merge 合并多个map
$button: map.merge(
(
'font-weight': getCssVar('font-weight-primary'), // 字体加粗
),
$button
);
// 将参数变成列表
// 使用 var 来获取变量值 (--el-font-weight-primary)
// 这个值在全局作用域 定义的
// :root {
// @include set-css-var-value('font-weight-primary', 500);
// }
@function getCssVar ($args...) {
@return var(#{joinVarName($args)})
}
// 拼接字符串 --el-font-weight-primary
@function joinVarName ($list) {
$name: '--' + config.$namespace;
@each $item in $list {
@if $item != '' {
$name: $name + '-' + $item;
}
}
@return $name;
}
// el-button
@include b(button) {
@include set-component-css-var('button', $button);
}
组件添加类名
通过方法 返回字符串的方式来给组件绑定类名。
<button
:class="[
ns.b(),
ns.m(type),
ns.m(size),
ns.is('disabled', disabled),
ns.is('plain', plain),
ns.is('round', round),
ns.is('circle', circle),
ns.is('loading', loading)
]"
>
<slot />
</button>
ns 方法实现
const defaultNamespace = 'el'
// 连接字符串
const _bem = (
namespace: string,
block: string,
blockSuffix: string,
element: string,
modifier: string
) => {
let cls = `${namespace}-${block}`
if (blockSuffix) {
cls += `-${blockSuffix}`
}
if (element) {
cls += `__${element}`
}
if (modifier) {
cls += `--${modifier}`
}
return cls
}
const useNamespace = (block: string) => {
// 命名空间
const namespace = computed(() => defaultNamespace)
// block 接收一个块的前缀 返回 ${namespace}-${block}-${blockSuffix}
const b = (blockSuffix = '') => _bem(unref(namespace), block, blockSuffix, '', '')
// element 接收元素名 返回 ${namespace}-${block}__${element}
const e = (element?: string) => element ? _bem(unref(namespace), block, '', element, '') : ''
// element 接收元素名 返回 ${namespace}-${block}--${modifier}
const m = (modifier?: string) => modifier ? _bem(unref(namespace), block, '', '', modifier) : ''
// 返回 ${namespace}-${block}-${blockSuffix}__${element}
const be = (blockSuffix?: string, element?: string) =>
blockSuffix && element
? _bem(unref(namespace), block, blockSuffix, element, '')
: ''
// 返回 ${namespace}-${block}__${element}--${modifier}
const em = (element?: string, modifier?: string) =>
element && modifier
? _bem(unref(namespace), block, '', element, modifier)
: ''
// 返回 ${namespace}-${block}-${blockSuffix}--${modifier}
const bm = (blockSuffix?: string, modifier?: string) =>
blockSuffix && modifier
? _bem(unref(namespace), block, blockSuffix, '', modifier)
: ''
// 返回 ${namespace}-${block}-${blockSuffix}__${element}--${modifier}
const bem = (blockSuffix?: string, element?: string, modifier?: string) =>
blockSuffix && element && modifier
? _bem(unref(namespace), block, blockSuffix, element, modifier)
: ''
// 如果 name 和 state 存在则返回 is${name} 否则返回 ''
const is: {
(name: string, state: boolean | undefined): string
(name: string): string
} = (name: string, ...args: [boolean | undefined] | []): string => {
const state = args.length >= 1 ? args[0]! : true
return name && state ? `${statePrefix}${name}` : ''
}
return {
namespace,
b,
e,
m,
be,
em,
bm,
bem,
is
}
}
总结
-
通过
BEM规范实现样式书写 -
变量的封装:
- 通过 连接字符串 拼接变量;
- 通过
:root{}和 当前组件的作用域 实现 全局变量和组件变量; - 使用
map快速生成 多个变量; - 通过
var+ 字符串拼接的方式获取变量值。
-
html元素通过函数生成字符串的方式 绑定类名。