CSS 自定义属性(vars)与 SASS/SCSS,一种实用的架构策略

2025-06-05

CSS 自定义属性(vars)与 SASS/SCSS,一种实用的架构策略

如果您还不了解 CSS 自定义属性,那您真的应该学习一下。我尤其倾向于尽可能使用 CSS 自定义属性,而不是 SASS 变量,主要是因为它的反应性。在本文中,我假设您已经熟悉 CSS 自定义属性和 SASS 变量(使用 SCSS 语法)。

有时,使用 CSS 自定义属性并非出于个人喜好,而是一种必需。它非常适合构建主题、动态组件、可配置页面等。但随着页面规模的增长,代码库的维护复杂性和创建模块迷宫的风险也会随之增加。

处理 SASS/SCSS 变量时,一种常见的方法是创建一个 _vars 文件,用于保存我们应用中的大多数变量。如果是 CSS 自定义属性,则还需要添加前缀以避免冲突。这里的想法是使用 CSS 自定义属性,但我认为它也适用于 SASS 变量。以下是一种常见的模式:

$prefix: pf;

:root {
  --#{$prefix}-primary-color: #000000; 
  // this will compile to: --pf-primary-color: #000000;
}
Enter fullscreen mode Exit fullscreen mode

现在我们在样式表中使用它:

@import './config/_vars';

.btn-example {
  background-color: var(--#{$prefix}-primary-color);
}
Enter fullscreen mode Exit fullscreen mode

很好,你分离了关注点,使用前缀避免了冲突,或许还可以_vars在主文件中添加 ,以确保变量在样式表中始终可用。很好,但这种方法存在一些严重的问题:

  1. 在 SCSS 中使用带前缀的 CSS 自定义属性时,你必须手动添加前缀--#{$prefix}-,这很烦人。如果你要对属性进行归因,那就更麻烦了color: var(--#{$prefix}-color);

  2. 你最终会大量复制粘贴代码,这会增加代码库出错的概率。或者,最终有人会添加硬编码的前缀。

  3. 像变量这样的符号--#{$prefix}...很丑陋,对新开发人员来说也是个问题。他们可能会忘记前缀,或者不喜欢只写一个简单的 var 的语法等等。你可能会想:“好吧,他们会不喜欢,谁在乎呢……”。好吧,你应该在乎,但实际上,如果你的团队不喜欢他们正在做的事情,他们最终会试图在代码上作弊,只是为了尽快摆脱任务。

  4. 所以你决定分离关注点。你以为你已经分离了关注点,因为你把变量从样式中分离出来,又把元素按模块拆分出来,但实际上并没有。所有变量都放在同一个文件中,所以一个简单的按钮样式会消耗与整个应用程序相关的文件中的变量。

  5. 然后你说:“好的,我为每个关注点创建一个变量集合文件,就这样。” 最终你不得不导入所有这些文件(其实是一样的),或者不得不在编辑器中打开许多文件来编辑一个属性:你打开按钮样式表,发现按钮是由一个主文件导入的,而主文件又导入了一些变量——或者是一个配置文件——然后你打开那个配置文件,发现配置文件导入了一个 _btn_vars ,并且该文件使用了一些从……导入的辅助函数。然后你去喝杯咖啡。这将是漫长的一天。

  6. 如果您尝试按元素拆分样式表,当尝试将元素分离为模块时(_buttons.scss例如),因为它使用一些变量,您将需要导入包含整个应用程序变量的 _vars 文件,以便在按钮中使用一些变量。

一个男人深深地专注于自己的思想

那么,我们能做什么呢?

我并不是说这种方法完全不适用。但对于可扩展的大型应用程序来说,上述架构很快就会成为问题,或者在最好的情况下,它会因为代码重复而不必要地增加 CSS 文件的大小。因此,以下是我对此的看法:

首先,为 CSS 自定义属性定义 getter 和 setter。然后,使用 getter 和 setter 来处理应用中的变量,从而提供模式和可靠性。现在,你就可以真正地在一个可靠的架构中分离关注点了。让我们看看如何操作:

声明自定义属性

让我们开始添加一个 @mixin 来声明一组 CSS 自定义属性。这个 mixin 将用于为变量添加前缀,并将它们添加到代码中。它还能让我们的应用真正具备可扩展性和易于维护的特性。如果您需要修改变量在应用中的工作方式,无需打开所有文件进行修改,也无需反复检查是否有问题,只需调整该 mixin 即可。它对新开发者也很友好,可以避免在声明属性时出现错误(例如语法错误、忘记前缀等):

/**
 * Use this mixin to declare a set of CSS Custom Properties.
 * The variables in $css_variables will be properly prefixed.
 * The use of this mixin is encoraged to keep a good scalability.
 *
 * Usage:
 *
 * @include cssvars((
 *  base-font-size: 65.5%,
 *  font-family: #{"HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif},
 * 
 *  primary-color: #33b5e5,
 *  secondary-color: #ff500a,
 * ));
 *
 * Will result in
 *
 * root {
 *    --prefix-var-name: value;
 *    --prefix-var-name: value;
 *    --prefix-var-name: value;
 * }
 *
 */
@mixin cssvars($css_variables, $prefix: pf) {
    :root {
        @each $name, $value in $css_variables {
            --#{$prefix}-#{$name}: #{$value};
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

现在,不要执行并维护此操作:

:root {
  --#{$prefix}-base-font-size: 65.5%;
  --#{$prefix}-font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;

  --#{$prefix}-primary-color: #33b5e5;
  --#{$prefix}-secondary-color: #ff500a;
}
Enter fullscreen mode Exit fullscreen mode

您将做到并保持这一点:

 @include cssvars((
   base-font-size: 65.5%,
   font-family: #{"HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif},

   primary-color: #33b5e5,
   secondary-color: #ff500a,
 ));
Enter fullscreen mode Exit fullscreen mode

结果是一样的:

:root {
  --pf-base-font-size: 65.5%;
  --pf-font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --pf-primary-color: #33b5e5;
  --pf-secondary-color: #ff500a;
}
Enter fullscreen mode Exit fullscreen mode

你也可以通过传递另一个前缀作为第二个参数来覆盖它。注意,对于这里展示的所有函数和混合宏, $prefix 都将作为带有默认值的参数出现。我更喜欢这样使用,但如果你愿意,也可以将其取出并使用单个 $prefix 变量。

也许您可以添加一个新参数来覆盖“:root”选择器,创建范围自定义属性声明,我不知道...您可以调整这个混合器以适合您的风格和需求,这里的想法是为您的自定义属性提供一个设置器,让您在处理变量时拥有一个模式和单一事实来源,从而实现应用程序的稳健增长。

获取自定义属性

让我们创建一个 getter 来检索我们的 CSS 自定义属性,避免使用奇怪的语法和密集的代码:

/**
 * Retrieve a css variable value with prefix
 *
 * Usage
 *
 * .selector {
 *   color: cssva(primary-color);
 * }
 *
 * Will result in
 *
 * .selector {
 *    color: var(--prefix-primary-color);
 * }
 */
@function cssvar($name, $prefix: pm) {
    @return var(--#{$prefix}-#{$name});
}
Enter fullscreen mode Exit fullscreen mode

现在,不再是这样:

:root {
  --#{$prefix}-button-height: 40px;
  --#{$prefix}-button-color: #ffffff;
  --#{$prefix}-button-background: #000000;

  .button-primary {
    height: var(--#{$prefix}-button-height);
    line-height: var(--#{$prefix}-button-height);
    color: var(--#{$prefix}-button-color);
    background-color: var(--#{$prefix}-button-background);
  }
}
Enter fullscreen mode Exit fullscreen mode

我们得到这个:

@include cssvars((
  button-height: 40px,
  button-color: #ffffff,
  button-background: #000000,
));

.btn-primary {
  height: cssvar(button-height);
  line-height: cssvar(button-height);
  color: cssvar(button-color);
  background-color: cssvar(button-background);
}
Enter fullscreen mode Exit fullscreen mode

这将导致:

:root {
  --pm-button-height: 40px;
  --pm-button-color: #ffffff;
  --pm-button-background: #000000;
}

.btn-primary {
  height: var(--pm-button-height);
  line-height: var(--pm-button-height);
  color: var(--pm-button-color);
  background-color: var(--pm-button-height);
}
Enter fullscreen mode Exit fullscreen mode

语法、可读性和维护性均有所改进。
另请注意,此处提到的前缀是指变量名的前缀,例如--prefix-color:blue,而不是 CSS 原生属性的前缀,例如-moz-*

更新自定义属性

假设我们想添加一个big button按钮。很简单,我们只需在类中将 button-height 变量的值覆盖为更大的值即可。因此,我们需要一个 mixin 来更新(覆盖)自定义属性值。为了简洁起见,请添加以下 mixin:

@mixin cssvar ($name, $value: '', $prefix: pm) {
    --#{$prefix}-#{$name}: #{$value};
}
Enter fullscreen mode Exit fullscreen mode

现在,不再是这样:

 @include cssvars((...));

.btn-primary {
  height: cssvar(button-height);
  line-height: cssvar(button-height);
  color: cssvar(button-color);
  background-color: cssvar(button-background);
  &--big {
    // ** LOOK HERE **
    --#{$prefix}-button-height: 56px;
  }
}
Enter fullscreen mode Exit fullscreen mode

我们得到这个:

 @include cssvars((...));

.btn-primary {
  height: cssvar(button-height);
  line-height: cssvar(button-height);
  color: cssvar(button-color);
  background-color: cssvar(button-background);
  &--big {
    // ** LOOK HERE **
    @include cssvar(button-height, 56px);
  }
}
Enter fullscreen mode Exit fullscreen mode

结果如下:

:root { ...vars }

.btn-primary {
  height: var(--pm-button-height);
  line-height: var(--pm-button-height);
  color: var(--pm-button-color);
  background-color: var(--pm-button-height);
}

.btn-primary--big {
  --pm-button-height: 56px;
}
Enter fullscreen mode Exit fullscreen mode

现在我们有了一套强大的工具集:)

组织提案

现在我们有了一套很酷的辅助函数,快来使用吧!以下是我的小贴士:

  1. 创建一个带有此处显示的功能和混合的 _helpers 文件,让您轻松处理 CSS 自定义属性。

  2. 创建一个 _config 文件来保存应用程序全局自定义属性,这些属性与整个应用程序有关,例如原色和次色、字体系列、基本字体大小、容器宽度等。

  3. 现在,根据关注点创建单独的样式表,并将它们作为独立的整体。例如,对于按钮,您必须创建一个 _buttons.scss 文件,其中包含所有变量、选择器、属性、样式等。如果您有大量按钮,请将这些文件放在“buttons”文件夹中,并将不同样式拆分成新文件,并将它们合并到按钮文件夹的主文件中。表单、排版等也同样如此。

  4. 在主文件中调用_helpers_config以及所有应用程序(按钮、表单、拼写错误……)。您也可以创建一个仅包含 _helpers 和 _config 的文件,然后分别编译模块样式表,最后添加核心和所需的模块。modulescore.css

最后,您将拥有一个几乎像这样的文件夹/文件树:

styles
   config
       _helpers.scss
       _config.scss
   modules
       _buttons.scss
       _form.scss
       _typography.scss
       _tables.scss
   main.scss
Enter fullscreen mode Exit fullscreen mode

在运行时,正如我们讨论的 CSS 自定义属性一样,一旦在 :root {} 中声明,所有变量都将在其上下文中全局可用。您可以在任何地方使用它们,而无需使用大量的 @import。它们就是可用的。当然,这有时是一件坏事,具体取决于您的代码库大小,并且可能会损害您的性能——或者,如果您不需要动态变量,您可以使用 SASS 变量,但要将它们放在正确的位置。我的意思是,您可以轻松调整此架构来限定自定义属性的范围。

我们的 main.scss 几乎是这样的:

@import './config/_helpers';
@import './config/_config';

@import './modules/_typography';
@import './modules/_buttons';
@import './modules/_form';
@import './modules/_tables';

// etc
Enter fullscreen mode Exit fullscreen mode

这将是一个模块:

@include cssvars((
    btn-primary-text-color: #ffffff,
    btn-secondary-text-color: #ffffff,
    btn-border-radius: 4px,
    btn-text-transform: uppercase,
    btn-font-size: 12px,
    btn-font-weight: 600,
    btn-height: 40px,
    btn-padding: 0 30px,
));

.button, button,
input[type="button"],
input[type="submit"],
input[type="reset"] {
        // { hard coded properties }
    height: cssvar(btn-height);
    padding: cssvar(btn-padding);
    line-height: cssvar(btn-height);
    font-size: cssvar(btn-font-size);
    font-weight: cssvar(btn-font-weight);
    letter-spacing: cssvar(btn-font-weight);
    color: cssvar(primary-color);
    border-radius: cssvar(btn-border-radius);
    border: 1px solid cssvar(primary-color);
    text-transform: cssvar(btn-text-transform);
    &.focus, &:focus,
    &.hover, &:hover {
        // { ... whatever you want }
    }
    &.button-block {
        display: block;
        width: 100%;
    }
    &.button-primary {
        color: cssvar(btn-primary-text-color);
        background-color: cssvar(primary-color);
        border-color: cssvar(primary-color);
    }
    &.button-secondary {
        color: cssvar(btn-secondary-text-color);
        background-color: cssvar(secondary-color);
        border-color: cssvar(secondary-color);
    }
    &.button--big {
        @include cssvar(btn-height, 56px);
    }
}
Enter fullscreen mode Exit fullscreen mode

这只是一个例子。按钮本身可能更复杂。但请注意,按钮所需的所有内容都包含在按钮本身中。导入后,其自定义属性将在上下文中全局可用。另外,我们还使用了辅助函数来提供模式和可靠性。

如何解决前 6 个问题

在本文开头,我讨论了 SCSS 代码库中的 6 个常见问题。以下是解决这些问题的方法:

现在我们有一个漂亮而干净的语法来处理带前缀的道具,没有必要复制粘贴代码来提高速度,使用我们的助手很容易和快速,改变和维护我们的 css 道具真的很有趣,而且它们也是带前缀和标准化的。

让新开发者轻松安全地加入代码库非常容易。说到变量,我们主要有 3 个函数来处理变量并向他们解释。_config 文件保存了应用程序的全局变量,其他变量则位于模块本身:

// setter
@include cssvars((...));

// updater
@include cssvar(name, value);

// getter fn
prop: cssvar(name);
Enter fullscreen mode Exit fullscreen mode

真正做到了关注点分离。我们将所有模块都视为一个整体,所有声明都在这里,并且只提供输出。如果需要拆分模块,它会被放到一个包含主导出文件的文件夹中,无需在项目中费力地寻找所有文件的位置。

结论

这里展示的函数和混合宏演示了如何为应用程序提供一个稳健的增长架构。但这并非万能的方案,你当然可以根据自己的需求进行修改,例如更改名称和参数。这只是一个想法的火花,你可以根据需要进行调整,最终目的是帮助应用程序轻松扩展 :)

封面照片由 Tierra Mallorca 在 Unsplash 上拍摄

文章来源:https://dev.to/felipperegazio/css-custom-properties-vars-with-sass-scss-a-practical-architecture-strategy-1m88
PREV
如何创建一个简单的蜜罐来保护您的表单免受垃圾邮件发送者的攻击
NEXT
使用 HTML、CSS 和 JS 创建侧边栏