使用 CSS 变量如何帮助我减少 JavaScript

2025-06-08

使用 CSS 变量如何帮助我减少 JavaScript

注:我的这篇文章受到了Chris Coyier 这篇关于 CSS 强大功能的文章calc()的启发。绝对值得一读!

如果您从 2018 年开始关注 Web 开发,那么您可能遇到过CSS 自定义属性/变量。它们已经成为应用程序中的新热点,甚至超越了使用原始 CSS 的范围,因为它们提供了作用域和级联功能,即使是像Styled Components这样的新型 CSS-in-JS 解决方案也无法直接复制。

当我第一次了解 CSS 自定义属性时,我并没有对它们给予太多的公平对待,因为我已经成为了一个 props slinging、CSS-in-JS 的爱好者(请保留你的批评😛),但是我最近的项目要求我回到传统的样式表,因为使用了外来框架SvelteJS

起初,仅仅声明样式而不使用任何形式的变量似乎还算可以接受;每当需要根据代码自定义某些内容时,我只需编写一串内联 CSS 并将其添加到我的元素上,而不必过多担心优化。然而,虽然网站看起来仍然很流畅,但我的代码库却越来越难以阅读。于是,CSS 变量应运而生!

附注:曾经有人会大声疾呼:“它们不叫变量;它们是自定义属性! ” 幸好,相关的 MDN 文档和通用术语已经跟上来了,直接叫它们变量。所以两种说法都可以,但我认为“变量”更清晰一些😊

那么 CSS 变量如何工作?

对于不熟悉的人来说,你可以在任何元素中声明变量,也可以在:root选择器中声明变量,使其全局可访问。只需使用--[property-name]语法,CSS 就会将其识别为变量……

:root {
    --global-var: 50px;
}
.some-class {
    --scoped-var: 4%;
}
Enter fullscreen mode Exit fullscreen mode

...然后在子元素(或任何元素的全局属性)中使用这些变量var(--[property-name])

.child-of .some-class {
    margin: var(--scoped-var);
}
Enter fullscreen mode Exit fullscreen mode

这与CSS 属性的工作方式类似,不同之处在于 CSS 变量可以采用您选择的任何度量单位,并用于定义任何 CSS 属性。这意味着您可以获得预处理器多年来提供的相同灵活性,尽管语法略显笨拙(嘿,这就是 CSS3 🤷‍♀)。

鲜为人知的是,CSS 变量可以无单位化。乍一看这似乎没什么大不了的,但它带来了一个很大的优势:结合使用时calc(),CSS 变量可以用来按一定量缩放属性。这在重构代码时非常有用,因为这意味着我只需几行 CSS 计算代码就可以用 JavaScript 重写我的 CSS 字符串构造。

让我们看一个例子

为了展示自定义属性的实际作用,我将从我构建的投资组合概念中截取一个逻辑片段。

目标很简单:我希望强调条能够循环显示一组渐变,从一个渐变切换到另一个渐变。只需一个动画关键帧即可实现,但我有一个需要注意的事项:页面其他元素正在使用一个循环间隔,其逻辑 CSS 无法复制,为了保持一致性,我希望在强调条中使用相同的间隔。当然,这个间隔是在 JavaScript 中使用 定义的setInterval(...)。每当回调函数被调用时,都需要更改一些 CSS。这个间隔是在父组件中设置的,并在我的强调条组件中访问(是的,我使用的是基于组件的框架)。

在深入研究示例之前,请注意该项目是基于 Svelte 构建的。这应该不会对代码的可读性造成太大影响;只需接受一些细节涉及一些魔法即可。


最终目标

我以前的做法

最初,我通过创建一张隐藏溢出的宽背景图像来实现渐变的循环,然后在间隔的每个刻度上移动背景位置。这给人一种背景颜色变化的错觉,但本质上是在一次大的渐变中移动。然而,这个背景位置需要大量的计算。

为了简化跨多个组件的间隔跟踪,我跟踪了一个gradientIndex作为 prop 传递的变量。该索引对应于我正在循环使用的渐变颜色列表,名为GRADIENTS

然而,这意味着需要一些额外的逻辑来更新 CSS:每当gradientIndex发生更改时,都需要构造一个新的 CSS 字符串以将其应用为内联样式。因此,我们需要开发一个生命周期方法来在gradientIndexprop 发生变化时构造字符串。在 Svelte 中,这可以通过afterUpdate回调函数来实现:

...
afterUpdate(() => {
  backgroundPosition = `${(100 / (GRADIENTS.length - 1)) * gradientIndex}%`;
});
Enter fullscreen mode Exit fullscreen mode

我们还需要通过以下百分比来确定溢出的背景大小GRADIENTS.length

const backgroundSize = `${GRADIENTS.length * 200}% 100%`;
Enter fullscreen mode Exit fullscreen mode

最后,我们将其与我们构建的线性渐变背景一起放入内联样式中:

<span
  class="bar"
  style="background-image: {backgroundImage};
  background-position: {backgroundPosition};
  background-size: {backgroundSize}"
></span>
Enter fullscreen mode Exit fullscreen mode

所以,是的,最终结果运行良好,没有任何性能问题……至少在我那台性能超强的 MacBook 上是这样 😛 然而,我们增加了相当多的复杂性,而且随着规模的扩大,这些复杂性只会变得更糟。我们添加了一个生命周期方法来处理内联 CSS 的构造,并且在 JavaScript 中堆砌了大量变量,而这些变量理想情况下应该放在它们应该在的样式中。要是能用 CSS 来计算这些就好了!

一个新的、更易读的解决方案

那么,我们该如何使用 CSS 变量来解决这个问题呢?好吧,看看 JS 中构建的背景位置字符串,我们发现计算需要知道渐变数量(GRADIENTS.length)以及当前索引才能确定位置(gradientIndex)。那么,为什么不直接将每个 CSS 变量都创建成变量呢?

值得庆幸的是,CSS 变量可以像其他 CSS 属性一样使用内联样式进行设置(SASS 中的变量则不行!)。因此,假设上述两个变量都是组件状态的一部分。我们可以使用以下内联样式使它们对 CSS 可见:

<span
  class="bar"
  style="background-image: {backgroundImage};
  --index: {gradientIndex};
  --length: {gradientLength}"
></span>
Enter fullscreen mode Exit fullscreen mode

现在,我们可以通过使用以下方法在 CSS 中确定背景大小和位置calc()

.bar {
  --index: 0;
  --length: 0;
  background-size: calc(var(--length) * 200%) 100%;
  background-position: calc((100 / (var(--length) - 1)) * var(
  --index) * 1%);
}
Enter fullscreen mode Exit fullscreen mode

这里有几件事需要解释。首先,为了完整性,我们为每个变量设置一个初始值。由于内联样式应该始终应用,所以这不是必需的,尽管初始化 CSS 变量是一个好习惯。接下来,我们像设置 JS 变量一样设置背景位置,但有一个显著的区别:我们将变量--index乘以一个百分比,而不是直接在变量后面写百分号。这是因为calc()在数学中,变量被视为常数,因此必须将其乘以某个值才能应用度量单位。

哦,这是我们新的 JS 代码片段:
……等等,现在没有了!🎉

我们还能更深入地探讨吗?

这个例子没有利用变量级联。这对于基于组件的开发非常有用,因为你可以将许多复杂的 CSS 计算合并到父组件中。这样,子组件就可以直接从级联中更高层级的组件访问 CSS 变量。在我们的例子中,我们可以gradientIndex在父组件中创建一个 CSS 变量来包裹颜色条,从而完全避免将其作为 prop 传递!

当然,这可能会对代码的可读性产生负面影响,因为变量会在开发人员不知情的情况下向上几层级层叠下来。这暴露了层叠思维和基于组件的思维之间由来已久的冲突,因此请谨慎使用这种技术。

总结

由此可见,自定义属性在将 JavaScript 逻辑转移到样式表方面非常强大。此外,现在 CSS 变量已经兼容几乎所有现代浏览器(当然 IE 除外 😢),即使在生产代码中尝试使用它们也应该非常安全。所以,赶紧开始设计样式吧!

学到一点东西吗?

太棒了!万一你错过了,我特意开通了“网络魔法”简报,来探索更多类似的知识!

这东西探讨的是Web 开发的“首要原则”。换句话说,究竟是哪些糟糕的浏览器 API、扭曲的 CSS 规则以及半无障碍的 HTML 支撑着我们所有的 Web 项目?如果你想要超越框架,那么亲爱的 Web 魔法师,这东西就是为你准备的🔮

赶紧在这里订阅吧!我保证永远教书,绝不会发垃圾信息❤️

鏂囩珷鏉ユ簮锛�https://dev.to/bholmesdev/how-using-css-variables-cut-down-on-my-javascript-dc4
PREV
HTML5 标签 - 它们如何工作?我应该使用哪些?从定义开始 重要标签
NEXT
离开 Notion,在 VS Code 中构建第二个大脑