解析 Element Plus SCSS 变量:Sass 特性的精妙运用

420 阅读4分钟

Element Plus 默认提供一套主题,我们可以通过修改SCSS 变量来覆盖样式

  1. Sass 模块系统 (@use)

    @use 'sass:math';
    @use 'sass:map';
    @use 'sass:color';
    @use '../mixins/function.scss' as *;
    
    • @use 'sass:math';: 引入 Sass 的数学模块,允许进行算术运算,例如在 set-color-mix-level mixin 中看到的 math.div()math.percentage(),用于精确计算颜色混合的百分比。
    • @use 'sass:map';: 引入 Sass 的 Map (关联数组) 模块。这是 Element Plus 主题系统中最为核心的功能之一。通过 map.get()map.deep-merge()map.merge() 等函数,可以方便地定义、获取和合并复杂的颜色、字体、边框等样式变量集合。例如 $colors$font-size 等都是通过 Map 来管理的。
    • @use 'sass:color';: 引入 Sass 的颜色模块,提供了强大的颜色操作函数,如 color.mix() 用于混合两种颜色,生成不同深浅的色阶(例如 primary color 的 light-* 和 dark-* 系列)。
    • @use '../mixins/function.scss' as \*;: 导入自定义的 mixins 和 functions。as * 的用法表示将该模块下的所有成员(mixins 和 functions)导入到当前命名空间,可以直接使用,例如 getCssVar() 函数。

    @use 规则取代了旧的 @import 规则,它提供了更好的命名空间管理,避免了全局变量和选择器冲突,使得样式代码更加健壮和可维护。

  2. 变量系统($variable) 与默认值 !default机制

    sass 变量是定义可复用值的基本方式,例如颜色、字体栈或任何 CSS 值。

    $types: primary, success, warning, danger, error, info;
    $border-width: 1px !default;
    $border-style: solid !default;
    

    $types: 这是一个 Sass 列表 (List),存储了系统中定义的主要状态类型。它常用于 @each 循环中,为每种类型生成对应的样式。

    !default 标志: 这个标志非常重要。它意味着如果该变量尚未被赋值,则使用当前这个值;如果它已经被外部(例如用户自定义的主题文件)赋值,则当前这条赋值语句将被忽略。这为 Element Plus 的主题定制提供了极大的灵活性,用户可以在不修改源码的情况下覆盖默认变量值。几乎所有的基础颜色、尺寸、边框等变量都使用了 !default

  3. Maps:结构化数据管理

    Maps 是 Sass 中用于组织和访问键值对数据的强大工具。Element Plus 大量使用 Maps 来定义组件的各种样式属性。

    $colors: () !default;
    $colors: map.deep-merge(
      (
        'white': #ffffff,
        'black': #000000,
        'primary': (
          'base': #409eff,
        ),
        // ...其他颜色
      ),
      $colors
    );
    
    $font-size: () !default;
    $font-size: map.merge(
      (
        'extra-large': 20px,
        // ...其他字号
      ),
      $font-size
    );
    

    初始化与合并: 通常先将 Map 初始化为空 () 并配合 !default,然后使用 map.deep-merge()map.merge() 来合并基础定义和可能的外部 $colors 覆盖。map.deep-merge() 可以递归合并嵌套的 Map,而 map.merge() 只合并顶层。

    访问: 使用 map.get($map, $key1, $key2, ...) 来获取 Map 中的值。例如 map.get($colors, 'primary', 'base') 获取 $colors Map 中 primary键下的 base 键对应的值。

    组件化变量: 文件中大量的组件特定变量,如 $checkbox$radio$button 等,都是通过 Map 定义的。这种方式使得组件的样式属性集中管理,结构清晰。

  4. Mixins (@mixin@include):代码复用与抽象

    Mixins 允许开发者定义可重用的 CSS 声明块。这对于生成重复模式的样式(如颜色变体)非常有用。

    @mixin set-color-mix-level(
      $type,
      $number,
      $mode: 'light',
      $mix-color: $color-white
    ) {
      $colors: map.deep-merge(
        (
          $type: (
            '#{$mode}-#{$number}':
              color.mix(
                $mix-color,
                map.get($colors, $type, 'base'),
                math.percentage(math.div($number, 10))
              ),
          ),
        ),
        $colors
      ) !global; // 注意这里的 !global
    }
    
    // 使用 mixin
    @each $type in $types {
      @for $i from 1 through 9 {
        @include set-color-mix-level($type, $i, 'light', $color-white);
      }
    }
    

    参数化: set-color-mix-level mixin 接受 $type (如 'primary')、$number (1-9)、$mode ('light' 或 'dark') 和 $mix-color (与白色或黑色混合) 作为参数。

    动态生成: 它动态地计算出新的颜色值,并将其合并到全局的 $colors Map 中。

    !global 标志: 在 mixin 内部修改一个在 mixin 外部定义的变量(如此处的 $colors),需要使用 !global 标志。这是一个需要谨慎使用的特性,但在这种动态生成主题色的场景下是合理的。

    插值 (#{}): 在选择器或属性名中使用变量时,需要使用插值,例如 '#{$mode}-#{$number}'

  5. 控制指令 (@each, @for):自动化样式生成

Sass 的控制指令使得基于变量和逻辑生成大量 CSS 规则成为可能。

  • @each $item in $list:

    @each $type in $types {
      // ... 为每种类型生成颜色变体
      @for $i from 1 through 9 {
        @include set-color-mix-level($type, $i, 'light', $color-white);
      }
      @include set-color-mix-level($type, 2, 'dark', $color-black);
    }
    

    这里,@each 遍历 $types 列表中的每一种颜色类型('primary', 'success' 等)。

  • @for $var from <start> through <end>:

    嵌套在 @each 循环内部的 @for 循环从 1 到 9,为每种主色生成 9 个亮色阶 (light-1light-9)。

这些循环与 set-color-mix-level mixin 结合,极大地减少了手动编写相似颜色声明的重复劳动,并确保了颜色系统的一致性。

  1. 函数 (function):自定义逻辑与值转换

    虽然 var.scss 主要依赖内建函数,但它也通过 @use '../mixins/function.scss' as *; 引入了自定义函数。其中一个关键的自定义函数是 getCssVar()

    $border-color-hover: getCssVar('text-color', 'disabled') !default;
    

    getCssVar() 用于生成 CSS 自定义属性 (CSS Variables) 的字符串表示,例如 var(--el-text-color-disabled)。这使得 Element Plus 能够同时利用 Sass 的预处理能力和 CSS 自定义属性的运行时动态性。Sass 变量在编译时确定,而 CSS 自定义属性可以在浏览器运行时被 JavaScript 修改或根据其他 CSS 规则改变。

    例如,getCssVar('text-color', 'disabled') 会被编译成 var(--el-text-color-disabled)

    // joinVarName(('button', 'text-color')) => '--el-button-text-color',这里的el可以通过namespace修改
    @function joinVarName($list) {
      $name: '--' + config.$namespace;
      @each $item in $list {
        @if $item != '' {
          $name: $name + '-' + $item;
        }
      }
      @return $name;
    }
    
    // getCssVar('button', 'text-color') => var(--el-button-text-color)
    @function getCssVar($args...) {
      @return var(#{joinVarName($args)});
    }
    
  2. 组件变量的结构化管理

    $checkbox: () !default;
    $checkbox: map.merge(
      (
        'font-size': 14px,
        'font-weight': getCssVar('font-weight-primary'),
        'text-color': getCssVar('text-color-regular'),
        // ...
      ),
      $checkbox
    );
    

    对于每个UI组件,Element Plus都定义了专门的变量映射,包含该组件的所有可配置属性。这种结构化管理方式有几个优势:

    1. 变量命名空间隔离,避免冲突
    2. 通过 getCssVar 函数引用全局变量,保持一致性
    3. 支持组件级别的主题定制
  3. 响应式设计变量系统

    $sm: 768px !default;
    // ... 其他断点
    $breakpoints: (
      'xs': '(max-width: #{$sm - 1})',
      'sm': '(min-width: #{$sm})',
      // ...
    ) !default;
    

    这些变量和 Map 可以在其他 SCSS 文件中与 @media 规则结合使用,以创建响应式的布局和组件样式。

    $button-padding-vertical: () !default;
    $button-padding-vertical: map.merge(
      (
        'large': 13px,
        'default': 9px,
        'small': 6px,
      ),
      $button-padding-vertical
    );
    

    通过为不同尺寸定义对应的属性值,Element Plus实现了组件的响应式设计。这种方法使得组件可以根据不同场景(大、中、小尺寸)呈现适当的视觉效果,同时保持设计的一致性。