用ElementPLus的方式打开->🙌BEM设计模式
前言
在前端开始学习完框架后,大多数人应该还都和我一样觉得 CSS
有手就行,不就是记一些样式语句嘛!
其实不然,CSS
还有很多可以学习的,对于我们这个阶段的菜菜前端er🐣还是有必要学习了解在企业中是怎么写样式的?
然后我们就会发现,🙀CSS
居然还可以写变量!函数?其封装的复杂程度甚至不小于业务逻辑函数!不过在此之前我们需要翻阅 Sass的官方文档 来简单学习一下
没错,接下来我们将效仿 ElementPlus 的文件结构,源码结构来解析一下成熟组件库项目所使用的 CSS 架构模式之 BEM,来提高我们 CSS
代码的健壮性和可维护性。
BEM架构是什么🤔?
在软件领域有很多的架构思想,通过不同的架构模式,可以让你的软件工程易拓展、易维护、易复用。而 BEM 就是CSS的一个设计模式,它的主要作用是方便开发团队对前端项目的样式命名进行统一化处理,加强后期的维护工作。
浅谈BEM规范🗨️
BEM 规范下 classname 的命名格式为:
block-name__<element-name>--<modifier-name>_<modifier_value>
- 所有实体的命名均使用小写字母,复合词使用连字符 “-” 连接。
- Block 与 Element 之间使用双下画线 “__” 连接。
- Mofifier 与 Block/Element 使用双连接符 “--” 连接。
- modifier-name 和 modifier_value 之间使用单下画线 “_” 连接。
当然这些规则并不一定需要严格遵守的,也可以根据你的团队风格进行修改。 用开发者工具查看ElementPlus的组件
通过 JS 生成 BEM 规范类名🕺
在上面我们知道了 BEM
样式的规范,那么可以知道 BEM
命名规范是具有一定规律性的,就是需要对于 el
、-
、 __
、 --
这种固定的字符串进行拼接,所以我们可以通过 JavaScript
按照 BEM
命名规范进行动态生成。如下是 BEM
命名字符拼接函数
// BEM 命名字符拼接函数
const _bem = (
namespace: string,
block: string,
blockSuffix: string,
element: string,
modifier: string
) => {
// 默认是 Block
let cls = `${namespace}-${block}`
// 如果存在 Block 后缀,也就是 Block 里面还有 Block,例如:el-form 下面还有一个 el-form-item
if (blockSuffix) {
cls += `-${blockSuffix}`
}
// 如果存在元素
if (element) {
cls += `__${element}`
}
// 如果存在修改器
if (modifier) {
cls += `--${modifier}`
}
return cls
}
然后我们就可以定义一些函数去创建所需的类名了,下面是一些举例
export const useNamespace = (block: string) => {
// 命名前缀也就是命名空间
const namespace = computed(() => defaultNamespace)
// 创建块 例如:el-form
const b = (blockSuffix = '') =>
_bem(unref(namespace), block, blockSuffix, '', '')
// 创建元素 例如:el-input__inner
const e = (element?: string) =>
element ? _bem(unref(namespace), block, '', element, '') : ''
// 创建块修改器 例如:el-form--default
const m = (modifier?: string) =>
modifier ? _bem(unref(namespace), block, '', '', modifier) : ''
// 创建后缀块元素 例如:el-form-item
const be = (blockSuffix?: string, element?: string) =>
blockSuffix && element
? _bem(unref(namespace), block, blockSuffix, element, '')
: ''
return {
namespace,
b,
e,
m,
be,
}
}
- b函数 创建块 el-form、
- e函数 创建元素 el-input__inner、
- m函数 创建块修改器 el-form--default、
- be函数 创建块后缀元素 el-form-item、
具体引用及使用:
// 创建 classname 命名空间实例 const ns = useNamespace('button')
import { useNamespace, } from '@mmcat-ui/hooks'
const bem = useNamespace('button')
<template>
<button :class="[ bem.b() ]" >按钮</button>
<template>
代码表现:
通过 SCSS 生成 BEM 规范样式💃
解析文件结构及函数功能
我们在ElementPlus的源码的 packages
中可以找到 theme-chalk/src
样式文件夹,也可以在文章结尾我的组件库源码仓库中找到,让我们来看看它是如何运作的
common 文件夹
var.scss 基础样式定义
定义基础样式变量
// Color
$colors: () !default;
$colors: map.deep-merge(
(
"white": #ffffff,
"black": #000000,
"primary": (
"base": #409eff,
),
"success": (
"base": #67c23a,
),
"warning": (
"base": #e6a23c,
),
"danger": (
"base": #f56c6c,
),
"error": (
"base": #f56c6c,
),
"info": (
"base": #909399,
),
),
$colors
);
$text-color: () !default;
$text-color: map.merge(
(
"primary": #303133,
"regular": #606266,
"secondary": #909399,
"placeholder": #a8abb2,
"disabled": #c0c4cc,
),
$text-color
);
// Background
$bg-color: () !default;
$bg-color: map.merge(
(
"": #ffffff,
"page": #f2f3f5,
"overlay": #ffffff,
),
$bg-color
);
mixins 文件夹
mixins.scss 生成 BEM 类名选择器
用于生成bem架构的类名选择器
// 定义 Block
@mixin b($block) {
$B: $namespace + "-" + $block !global;
.#{$B} {
@content;
}
}
// 定义 Element
@mixin e($element) {
...
}
// 定义修改器
@mixin m($modifier) {
...
}
// 状态 is-checked
@mixin when($state) {
...
}
在组件样式混合定义中生成如下这种类名:
.el-button--success {
...
}
.el-input__wrapper{
...
}
.el-input__inner {
...
}
.el-button--danger.is-plain, .el-button--danger.is-text, .el-button--danger.is-link {
...
}
config.scss 分隔符配置
当然分隔符什么可以自己定义
$namespace: "mc" !default; // 所有的组件以mc开头,如 mc-input
$common-separator: "-" !default; // 公共的连接符
$element-separator: "__" !default; // 元素以__分割,如 mc-input__inner
$modifier-separator: "--" !default; // 修饰符以--分割,如 mc-input--mini
$state-prefix: "is-" !default; // 状态以is-开头,如 is-disabled
_var.scss 全局样式mixins函数
生成完整css语句
--mc-font-size-small: 13px;
在根元素:root
中注入,使这种基础样式全局生效
set-component-css-var($name, $variables)
用于设置根元素:root
的样式,也就是全局样式
set-component-css-var('font-size', $font-size) => '--mc-font-size-small: 13px;'
/*
$font-size: () !default;
$font-size: map.merge(
(
"extra-large": 20px,
"large": 18px,
"medium": 16px,
"base": 14px,
"small": 13px,
"extra-small": 12px,
),
$font-size
);
*/
set-css-var-value($name, $value)
也用于设置根元素:root
的样式,也就是全局样式 (只是不用遍历出对应的值,$value直接赋值)
set-css-var-value('color-white', $color-white) => --mc-color-white: #ffffff;
/*
$colors: () !default;
$colors: map.deep-merge(
(
"white": #ffffff,
),
$colors
);
$color-white: map.get($colors, "white") !default;
*/
set-css-color-type($colors, $type)
主要生成的是全局的color样式
set-css-color-type($colors, success) => --mc-color-success: #333333;
css-var-from-global($var, $gVar)
也同样是为了在根元素:root
的生成样式,不过该mixin函数主要使用在特殊样式,比如在_button.scss
中使用,将特殊样式全局生效
css-var-from-global(('button', 'active','bg-color'), ("color",'success',"dark-2"))
=>
--el-button-active-bg-color: var(--el-color-success-dark-2);
function.scss 工具函数
主要用于拼接出css语句 --el-button-text-color
joinVarName($list)
将$list
数组中的元素用-
连接,并且开头是--el,返回一个完整的css变量名
joinVarName(('button', 'active','bg-color')) => '--el-button-active-bg-color'
joinVarName(("color",'success',"dark-2")) => '--el-color-success-dark-2'
getCssVarName($args...)
将$args
参数传给joinVarName
拼接css变量名,返回一个完整的css变量名
getCssVarName('button', 'text-color') => '--el-button-text-color'
getCssVar($args...)
将$args
参数传给joinVarName
拼接css变量名,返回一个css表达式var(#{joinVarName($args)})
getCssVar('button', 'text-color') => var(--el-button-text-color)
_button.scss 特殊样式mixins函数
定义一些mixin函数,用于生成属于button的特殊样式
// button特殊样式
.el-button--success {
--el-button-hover-bg-color: #ffffff;
}
.el-button:hover, .el-button:focus {
color: var(--el-button-hover-text-color);
border-color: var(--el-button-hover-border-color);
background-color: var(--el-button-hover-bg-color);
outline: none;
}
button-plain($type)
type是success、warning、danger、info<br/>在.el−button−−type 是 success、warning、danger、info<br />在.el-button--type是success、warning、danger、info<br/>在.el−button−−type 中生成对应plain样式,在button-variant中注入
// 最终生成:
.el-button--success {
--el-button-hover-bg-color: #ffffff;
}
button.scss 组件样式混合定义
通过@include
混入所有该组件的样式,同时制造出对应的组件类名选择器,通过map.get()
直接继承全局样式,用getCssVar()
间接继承全局样式
/*
common/var.scss
*/
// Button
// css3 var in packages/theme-chalk/src/button.scss
$button: () !default;
$button: map.merge(
(
"bg-color": getCssVar("fill-color", "blank"),
),
$button
);
// Input
// css3 var in packages/theme-chalk/src/input.scss
$input-height: () !default;
$input-height: map.merge(
(
"large": 40px,
"default": 32px,
"small": 24px,
),
$input-height
);
/*
button.scss
*/
@include b(button) {
@include set-component-css-var("button", $button);
}
@include b(button) {
height: map.get($input-height, "default");
background-color: getCssVar("button", "bg-color");
}
base.scss 转发全局基础样式
转发全局基础样式
@use "var.scss";
index.scss 转发所有样式
// root styles
@use "./base.scss";
// component styles
@use "./icon.scss";
@use "./button.scss";
var.scss 全局样式定义
// common
:root {
@include set-css-var-value("color-white", $color-white);
@include set-css-var-value("color-black", $color-black);
// Typography
@include set-component-css-var("font-size", $font-size);
@include set-css-var-value("font-weight-primary", 500);
}
// for light
:root {
color-scheme: light;
// --el-color-#{$type}
// --el-color-#{$type}-light-{$i}
@each $type in (primary, success, warning, danger, info) {
@include set-css-color-type($colors, $type);
}
// color-scheme
// Background
// --el-bg-color-#{$type}
@include set-component-css-var("bg-color", $bg-color);
// --el-text-color-#{$type}
@include set-component-css-var("text-color", $text-color);
}
最后
源码仓库:mcmcCat/mmcat-ui: Vue3 + TS + Vite + Scss 开发的常用基础 UI 组件库,研究组件性能优化 (github.com)
转载自:https://juejin.cn/post/7282953307531001867