使用 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%;
}
...然后在子元素(或任何元素的全局属性)中使用这些变量var(--[property-name])
。
.child-of .some-class {
margin: var(--scoped-var);
}
这与CSS 属性的工作方式类似,不同之处在于 CSS 变量可以采用您选择的任何度量单位,并用于定义任何 CSS 属性。这意味着您可以获得预处理器多年来提供的相同灵活性,尽管语法略显笨拙(嘿,这就是 CSS3 🤷♀)。
鲜为人知的是,CSS 变量可以无单位化。乍一看这似乎没什么大不了的,但它带来了一个很大的优势:结合使用时calc()
,CSS 变量可以用来按一定量缩放属性。这在重构代码时非常有用,因为这意味着我只需几行 CSS 计算代码就可以用 JavaScript 重写我的 CSS 字符串构造。
让我们看一个例子
为了展示自定义属性的实际作用,我将从我构建的投资组合概念中截取一个逻辑片段。
目标很简单:我希望强调条能够循环显示一组渐变,从一个渐变切换到另一个渐变。只需一个动画关键帧即可实现,但我有一个需要注意的事项:页面其他元素正在使用一个循环间隔,其逻辑 CSS 无法复制,为了保持一致性,我希望在强调条中使用相同的间隔。当然,这个间隔是在 JavaScript 中使用 定义的setInterval(...)
。每当回调函数被调用时,都需要更改一些 CSS。这个间隔是在父组件中设置的,并在我的强调条组件中访问(是的,我使用的是基于组件的框架)。
在深入研究示例之前,请注意该项目是基于 Svelte 构建的。这应该不会对代码的可读性造成太大影响;只需接受一些细节涉及一些魔法即可。
我以前的做法
最初,我通过创建一张隐藏溢出的宽背景图像来实现渐变的循环,然后在间隔的每个刻度上移动背景位置。这给人一种背景颜色变化的错觉,但本质上是在一次大的渐变中移动。然而,这个背景位置需要大量的计算。
为了简化跨多个组件的间隔跟踪,我跟踪了一个gradientIndex
作为 prop 传递的变量。该索引对应于我正在循环使用的渐变颜色列表,名为GRADIENTS
。
然而,这意味着需要一些额外的逻辑来更新 CSS:每当gradientIndex
发生更改时,都需要构造一个新的 CSS 字符串以将其应用为内联样式。因此,我们需要开发一个生命周期方法来在gradientIndex
prop 发生变化时构造字符串。在 Svelte 中,这可以通过afterUpdate
回调函数来实现:
...
afterUpdate(() => {
backgroundPosition = `${(100 / (GRADIENTS.length - 1)) * gradientIndex}%`;
});
我们还需要通过以下百分比来确定溢出的背景大小GRADIENTS.length
:
const backgroundSize = `${GRADIENTS.length * 200}% 100%`;
最后,我们将其与我们构建的线性渐变背景一起放入内联样式中:
<span
class="bar"
style="background-image: {backgroundImage};
background-position: {backgroundPosition};
background-size: {backgroundSize}"
></span>
所以,是的,最终结果运行良好,没有任何性能问题……至少在我那台性能超强的 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>
现在,我们可以通过使用以下方法在 CSS 中确定背景大小和位置calc()
:
.bar {
--index: 0;
--length: 0;
background-size: calc(var(--length) * 200%) 100%;
background-position: calc((100 / (var(--length) - 1)) * var(
--index) * 1%);
}
这里有几件事需要解释。首先,为了完整性,我们为每个变量设置一个初始值。由于内联样式应该始终应用,所以这不是必需的,尽管初始化 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