盒模型
盒模型都是由四个部分组成的,分别是margin、border、padding和content。
标准盒模型和IE盒模型的区别在于设置width和height时,对应的范围不同。
- 标准盒模型的
width、height只包含了content - IE盒模型的的
width、height除了content本身,还包含了border、padding
通过修改元素的box-sizing属性来改变元素的盒模型
box-sizeing: content-box表示标准盒模型(默认值)box-sizeing: border-box表示IE盒模型(IE盒模型)
默认content-box
如果懒得计算宽高,使用border-box
BFC
BFC是块级格式上下文(Block Formatting Context,BFC),是CSS布局的一个概念,在BFC布局里面的元素不受外面元素影响。
创建BFC条件
- 设置浮动:
float有值并不为空 - 设置绝对定位:
position(absolute、fixed) overfilow值为:hidden、auto、scrolldisplay值为:inline-block、table-cell、table-caption、flex等
BFC作用:
- 解决
margin重叠问题:由于BFC是一个独立的区域,内部元素和外部元素互不影响,将两个元素变为BFC,就解决了margin重叠问题 - 创建自适应两栏布局:可以用来创建自适应两栏布局,左边宽高固定,右边宽度自适应。
- 解决高度塌陷问题:在子元素设置浮动后,父元素会发生高度的塌陷,也就是父元素的高度为0解决这个问题,只需要将父元素变成一个BFC。
CSS选择器和优先级
优先级
CSS 通过以下四个层级来计算优先级:
- 行内样式(inline) :1000
- ID 选择器数量:100 × n
- 类选择器、属性选择器、伪类选择器数量:10 × n
- 标签选择器、伪元素选择器数量:1 × n
BEM 规范及
BEM 基础概念
BEM(Block, Element, Modifier)是一种 CSS 命名方法论。
命名规范
.block {} // 块
.block__element {} // 元素
.block--modifier {} // 修饰符
.block__element--modifier {} // 元素+修饰符
参考element-plus
核心拼接函数 (_bem)
// 核心的 BEM 类名生成函数
const _bem = (
namespace: string,
block: string,
blockSuffix: string,
element: string,
modifier: string
): string => {
// 1. 构建基础块名:namespace-block
let cls = `${namespace}-${block}`
// 2. 如果有块后缀,添加:namespace-block-blockSuffix
if (blockSuffix) {
cls += `-${blockSuffix}`
}
// 3. 如果有元素,添加:__element
if (element) {
cls += `__${element}`
}
// 4. 如果有修饰符,添加:--modifier
if (modifier) {
cls += `--${modifier}`
}
return cls
}
例如,_bem('el', 'button', '', 'icon', 'primary')
会生成 el-button__icon--primary。
基于 _bem,useNamespace 提供了 b(), e(), m() 等一系列更易用的方法
import { computed } from 'vue'
import { useGlobalConfig } from '../use-global-config'
// 核心的 BEM 类名生成函数
const _bem = (
namespace: string,
block: string,
blockSuffix: string,
element: string,
modifier: string
): string => {
// 1. 构建基础块名:namespace-block
let cls = `${namespace}-${block}`
// 2. 如果有块后缀,添加:namespace-block-blockSuffix
if (blockSuffix) {
cls += `-${blockSuffix}`
}
// 3. 如果有元素,添加:__element
if (element) {
cls += `__${element}`
}
// 4. 如果有修饰符,添加:--modifier
if (modifier) {
cls += `--${modifier}`
}
return cls
}
// 状态类名前缀(如 is-disabled、is-loading)
const statePrefix = 'is-'
/**
* 创建 BEM 命名空间的完整实现
* @param block - 块名(如 'button'、'input')
* @returns BEM 工具对象
*/
export const useNamespace = (block: string) => {
// 从全局配置获取命名空间(默认 'el')
const globalConfig = useGlobalConfig('namespace')
const namespace = computed(() => {
return globalConfig.value || 'el'
})
// 1. 基本块:el-button
const b = (blockSuffix: string = '') => {
return _bem(namespace.value, block, blockSuffix, '', '')
}
// 2. 元素:el-button__icon
const e = (element?: string) => {
return element ? _bem(namespace.value, block, '', element, '') : ''
}
// 3. 修饰符:el-button--primary
const m = (modifier?: string) => {
return modifier ? _bem(namespace.value, block, '', '', modifier) : ''
}
// 4. 块 + 元素:el-button-group__content
// blockSuffix: 'group', element: 'content' => el-button-group__content
const be = (blockSuffix?: string, element?: string) => {
return blockSuffix && element
? _bem(namespace.value, block, blockSuffix, element, '')
: ''
}
// 5. 元素 + 修饰符:el-button__icon--primary
// element: 'icon', modifier: 'primary' => el-button__icon--primary
const em = (element?: string, modifier?: string) => {
return element && modifier
? _bem(namespace.value, block, '', element, modifier)
: ''
}
// 6. 块 + 修饰符:el-button--large
const bm = (blockSuffix?: string, modifier?: string) => {
return blockSuffix && modifier
? _bem(namespace.value, block, blockSuffix, '', modifier)
: ''
}
// 7. 完整的 BEM:el-button-group__icon--primary
// blockSuffix: 'group', element: 'icon', modifier: 'primary'
const bem = (blockSuffix?: string, element?: string, modifier?: string) => {
if (blockSuffix && element && modifier) {
return _bem(namespace.value, block, blockSuffix, element, modifier)
}
if (blockSuffix && element) {
return _bem(namespace.value, block, blockSuffix, element, '')
}
if (blockSuffix && modifier) {
return _bem(namespace.value, block, blockSuffix, '', modifier)
}
if (element && modifier) {
return _bem(namespace.value, block, '', element, modifier)
}
if (blockSuffix) {
return _bem(namespace.value, block, blockSuffix, '', '')
}
if (element) {
return _bem(namespace.value, block, '', element, '')
}
if (modifier) {
return _bem(namespace.value, block, '', '', modifier)
}
return _bem(namespace.value, block, '', '', '')
}
// 8. 状态类:is-disabled, is-loading
// 用于表示元素状态,不与 BEM 主类名直接连接
const is = {
// 添加状态类:condition 为 true 时返回类名
add: (name: string, condition: boolean | undefined) => {
return condition ? `${statePrefix}${name}` : ''
},
// 直接返回状态类名
name: (name: string) => {
return `${statePrefix}${name}`
}
}
// 9. 生成 CSS 变量名(用于主题定制)
// 如:--el-button-text-color
const cssVar = {
name: (name: string) => {
return `--${namespace.value}-${block}-${name}`
},
// 带块后缀的 CSS 变量
withBlock: (blockSuffix: string, name: string) => {
return `--${namespace.value}-${block}-${blockSuffix}-${name}`
}
}
// 10. 生成 CSS 变量对象(便于在 style 属性中使用)
// 如:{ '--el-button-text-color': '#fff' }
const cssVarBlock = (variables: Record<string, string>) => {
const styles: Record<string, string> = {}
Object.keys(variables).forEach(key => {
styles[`--${namespace.value}-${block}-${key}`] = variables[key]
})
return styles
}
return {
namespace,
b,
e,
m,
be,
em,
bm,
bem,
is,
cssVar,
cssVarBlock
}
}
// 类型定义
export type UseNamespaceReturn = ReturnType<typeof useNamespace>
useNamespace 内部通过 useGlobalConfig 读取,实现所有组件类名前缀(如 el- 改为 ep-)的全局切换。这非常利于微前端等场景的样式隔离。
Sass 核心:mixins 混入
在编写组件样式时,Element Plus 使用对应的 Sass 混入来生成 CSS,确保与 JS 生成的类名结构一致
// 定义 BEM 命名规范变量
$namespace: 'el' !default; // 命名空间前缀,默认 'el'
$element-separator: '__' !default; // 元素分隔符
$modifier-separator: '--' !default; // 修饰符分隔符
$state-prefix: 'is-' !default; // 状态前缀
// 全局变量,存储当前块的名称
$common-separator: '-' !default;
// 1. 块(Block)混入 - 定义组件块
@mixin b($block) {
$B: $namespace + '-' + $block !global; // 设置全局变量 $B
.#{$B} { // 生成如 .el-button 的选择器
@content;
}
}
// 2. 元素(Element)混入 - 定义块内的元素
@mixin e($element) {
$E: $element !global; // 设置全局变量 $E
$selector: &; // 获取父选择器
@if hitAllSpecialNestRule($selector) {
// 特殊嵌套规则处理
@at-root {
#{$selector} {
#{currentSelector()} {
@content;
}
}
}
} @else {
// 普通情况:直接拼接元素
@at-root {
#{$selector + $element-separator + $element} {
@content;
}
}
}
}
// 3. 修饰符(Modifier)混入 - 定义块或元素的变体
@mixin m($modifier) {
$selector: &;
@at-root {
#{$selector + $modifier-separator + $modifier} {
@content;
}
}
}
// 4. 当(When)混入 - 条件状态(类似于 is- 前缀)
@mixin when($state) {
$selector: &;
@at-root {
&#{'.' + $state-prefix + $state} {
@content;
}
}
}
// 5. 特殊选择器混入 - 处理 & 选择器
@mixin spec($selector) {
@at-root {
#{&}#{$selector} {
@content;
}
}
}
// 6. 伪类混入 - 处理伪类选择器
@mixin pseudo($pseudo) {
$selector: &;
@at-root {
#{&}#{':#{$pseudo}'} {
@content;
}
}
}
// 7. 创建 BEM 选择器的快捷方式
@mixin button-size-mixin($padding-vertical, $padding-horizontal) {
padding: $padding-vertical $padding-horizontal;
}
// 辅助函数:获取当前选择器
@function currentSelector() {
$currentSelector: '';
@each $selector in & {
$currentSelector: #{$currentSelector + $selector + $element-separator + $E + ','};
}
@return $currentSelector;
}
// 辅助函数:检查是否命中特殊嵌套规则
@function hitAllSpecialNestRule($selector) {
@return containsModifier($selector) or containWhenFlag($selector) or containPseudoClass($selector);
}
// 辅助函数:检查选择器是否包含修饰符
@function containsModifier($selector) {
$items: ();
@each $item in $selector {
@if str-index($item, $modifier-separator) {
@return true;
}
}
@return false;
}
// 辅助函数:检查选择器是否包含状态标志
@function containWhenFlag($selector) {
@return str-index(#{$selector}, '.' + $state-prefix);
}
// 辅助函数:检查选择器是否包含伪类
@function containPseudoClass($selector) {
@return str-index(#{$selector}, ':');
}
// 8. CSS 变量相关混入
@mixin set-css-var-type($name, $type, $variables) {
#{getCssVarName($name, $type)}: $variables;
}
@mixin set-css-var-value($name, $value) {
#{'--' + $namespace + '-' + $name}: $value;
}
@function getCssVar($args...) {
@return var(#{getCssVarName($args...)});
}
@function getCssVarName($args...) {
$name: '--' + $namespace;
@each $arg in $args {
@if $arg != '' {
$name: $name + '-' + $arg;
}
}
@return $name;
}
实际组件应用示例
<script setup lang="ts">
import { useNamespace } from './use-namespace'
const ns = useNamespace('button')
// 示例类名生成
const classes = {
basic: ns.b(), // 'el-button'
withModifier: ns.m('primary'), // 'el-button--primary'
element: ns.e('icon'), // 'el-button__icon'
blockElement: ns.be('group', 'item'), // 'el-button-group__item'
elementModifier: ns.em('icon', 'large'), // 'el-button__icon--large'
blockModifier: ns.bm('group', 'round'), // 'el-button-group--round'
fullBem: ns.bem('group', 'icon', 'primary'), // 'el-button-group__icon--primary'
state: ns.is.add('disabled', true), // 'is-disabled'
cssVarName: ns.cssVar.name('color') // '--el-button-color'
}
console.log(classes)
</script>
<template>
<!-- Button 组件示例 -->
<button
:class="[
ns.b(),
ns.m(type),
ns.is.add('disabled', disabled),
ns.is.add('loading', loading),
]"
:style="ns.cssVarBlock({
'bg-color': bgColor,
'text-color': textColor
})"
>
<!-- Button 图标 -->
<span :class="ns.e('icon')">
<slot name="icon" />
</span>
<!-- Button 内容 -->
<span :class="ns.e('content')">
<slot />
</span>
</button>
</template>
@use 'mixins/mixins' as *;
@use 'common/var' as *;
@include b(button) {
// 基础按钮样式 .el-button
display: inline-flex;
justify-content: center;
align-items: center;
line-height: 1;
height: map-get($input-height, 'default');
white-space: nowrap;
cursor: pointer;
color: getCssVar('button', 'text-color');
text-align: center;
box-sizing: border-box;
outline: none;
transition: .1s;
font-weight: getCssVar('font-weight', 'primary');
// 禁用状态 .el-button.is-disabled
@include when(disabled) {
&,
&:hover,
&:focus {
cursor: not-allowed;
background-image: none;
}
}
// 加载状态 .el-button.is-loading
@include when(loading) {
position: relative;
pointer-events: none;
&::before {
content: '';
position: absolute;
left: -1px;
top: -1px;
right: -1px;
bottom: -1px;
border-radius: inherit;
background-color: getCssVar('mask-color', 'extra-light');
}
}
// 按钮大小修饰符
// .el-button--large
@include m(large) {
@include button-size-mixin(
map-get($button-padding-vertical, 'large'),
map-get($button-padding-horizontal, 'large')
);
height: map-get($input-height, 'large');
font-size: map-get($font-size, 'large');
}
// .el-button--small
@include m(small) {
@include button-size-mixin(
map-get($button-padding-vertical, 'small'),
map-get($button-padding-horizontal, 'small')
);
height: map-get($input-height, 'small');
font-size: map-get($font-size, 'small');
}
// 按钮类型修饰符
// .el-button--primary
@include m(primary) {
@include css-var-from-global(('button', 'bg-color'), ('color', 'primary'));
&:hover {
background: getCssVar('button', 'hover-bg-color');
}
&:active {
background: getCssVar('button', 'active-bg-color');
}
}
// .el-button__icon
@include e(icon) {
display: flex;
justify-content: center;
align-items: center;
}
// .el-button__text
@include e(text) {
display: inline-block;
}
// 图标在文本右侧 .el-button__icon--right
@include em(icon, right) {
order: 1;
margin-left: 5px;
}
// 圆形按钮 .el-button--circle
@include m(circle) {
border-radius: 50%;
padding: map-get($button-padding-vertical, 'default');
}
// 圆角按钮 .el-button--round
@include m(round) {
border-radius: getCssVar('border-radius', 'round');
}
}
// 按钮组样式 .el-button-group
@include b(button-group) {
display: inline-flex;
vertical-align: middle;
// 按钮组中的按钮 .el-button-group > .el-button
& > .el-button {
position: relative;
// 相邻按钮合并边框
&:not(:first-child) {
margin-left: -1px;
}
// 圆角处理
&:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
&:last-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
&:not(:first-child):not(:last-child) {
border-radius: 0;
}
// 悬停和激活状态
&:hover,
&:focus {
z-index: 1;
}
@include when(active) {
z-index: 2;
}
}
}