CSS 自定义属性的范围(以及乐趣)

Avatar of Jhey Tompkins
Jhey Tompkins

DigitalOcean 提供适用于您旅程各个阶段的云产品。立即开始使用 200 美元的免费信用额度!

您可能已经对 CSS 变量有所了解。如果不是,这里有一个两秒钟的概述:它们实际上称为自定义属性,您可以在声明块中设置它们,例如 --size: 1em,并将它们用作值,例如 font-size: var(--size);,它们 与预处理器变量不同(例如,它们会级联),并且这里有 更多信息

但是,我们是否充分利用了它们?我们是否陷入旧的习惯,忽略了变量可以显著减少我们编写的代码量的机会?

这篇文章的灵感来自于我最近发布的一条关于使用 CSS 变量创建动态动画行为的推文。

CSS 变量很棒,对吧?但是范围功能经常被忽视。例如,看看这个演示,3 个不同的动画,但只定义了 1 个动画 💪 这意味着动态动画 😎 https://#/VN02NlC4G8 via @CodePen #CSS #animation #webdev #webdesign #coding pic.twitter.com/ig8baxr7F3

— Jhey @ NodeConfEU 2019 📍🇮🇪⌨️ (@jh3yy) 2019 年 11 月 5 日

让我们看几个 CSS 变量可以用来做一些我们可能没有考虑过的非常酷的事情的例子。

基本范围优势

最简单且可能最常见的例子是范围颜色。我们最喜欢的用来搭配颜色的组件是什么?按钮。😅

考虑一下主要按钮和辅助按钮的标准设置。让我们从使用 BEM 语法 的一些基本标记开始。

<button class="button button--primary">Primary</button>
<button class="button button--secondary">Secondary</button>

传统上,我们可能会使用以下方法来对它们进行样式设置

.button {
  padding: 1rem 1.25rem;
  color: #fff;
  font-weight: bold;
  font-size: 1.25rem;
  margin: 4px;
  transition: background 0.1s ease;
}

.button--primary {
  background: hsl(233, 100%, 50%);
  outline-color: hsl(233, 100%, 80%);
}

.button--primary:hover {
  background: hsl(233, 100%, 40%);
}

.button--primary:active {
  background: hsl(233, 100%, 30%);
}

.button--secondary {
  background: hsl(200, 100%, 50%);
  outline-color: hsl(200, 100%, 80%);
}

.button--secondary:hover {
  background: hsl(200, 100%, 40%);
}

.button--secondary:active {
  background: hsl(200, 100%, 30%);
}

对于这样一件并不特别复杂的事情,这实在太多了。我们没有添加很多样式,而且添加了很多规则来适应按钮的不同状态和颜色。我们可以使用范围变量来显著减少代码量。

在我们的示例中,两个按钮变体之间唯一的不同值是色调。然后让我们稍微重构一下代码。我们不会更改标记,但会稍微清理一下样式,得到以下结果

.button {
  padding: 1rem 1.25rem;
  color: #fff;
  font-weight: bold;
  font-size: 1.25rem;
  margin: 1rem;
  transition: background 0.1s ease;
  background: hsl(var(--hue), 100%, 50%);
  outline-color: hsl(var(--hue), 100%, 80%);

}
.button:hover {
  background: hsl(var(--hue), 100%, 40%);
}

.button:active {
  background: hsl(var(--hue), 100%, 30%);
}

.button--primary {
  --hue: 233;
}

.button--secondary {
  --hue: 200;
}

这不仅减少了代码量,而且使维护变得更加容易。在同一个地方更改核心按钮样式,它将更新所有变体!🙌

我可能会保留它,以方便想要使用这些按钮的开发人员。但是,我们可以更进一步。我们可以将变量内联到实际元素上,并完全删除类声明。😲

<button class="button" style="--hue: 233;">Primary</button>
<button class="button" style="--hue: 200;">Secondary</button>

现在我们不再需要这些了。👍

.button--primary {
  --hue: 233;
}

.button--secondary {
  --hue: 200;
}

将这些变量内联可能不适合您下一个设计系统或应用程序,但它确实打开了新的可能性。例如,如果我们有一个需要覆盖颜色的按钮实例。

button.button.button--primary(style=`--hue: 20;`) Overridden

享受内联变量的乐趣

另一个机会是享受一下它带来的乐趣。这是我在 CodePen 上创建的许多 Pens 中使用的技巧。😉

您可能正在编写简单的 HTML,但在很多情况下,您可能正在使用 React 等框架或 Pug 等预处理器来编写您的标记。这些解决方案允许您利用 JavaScript 来创建随机的内联变量。对于以下示例,我将使用 Pug。Pug 是一个基于缩进的 HTML 模板引擎。如果您不熟悉 Pug,请不要担心!我会尽量保持标记的简单性。

让我们从为我们的按钮随机化色调开始

button.button(style=`--hue: ${Math.random() * 360}`) First

使用 Pug,我们可以使用 ES6 模板字面量来内联随机化的 CSS 变量。💪

动画更改

因此,现在我们有机会为元素定义随机特征,我们还能做什么?嗯,一个被忽视的机会是动画。没错,我们无法像这样直接为变量设置动画

@keyframes grow {
  from { --scale: 1; }
  to   { --scale: 2; }
}

但我们可以基于范围变量创建动态动画。我们可以动态地更改动画行为!🤩

示例 1:兴奋的按钮

让我们创建一个按钮,它会漂浮在自己的地方,然后在我们悬停在其上时变得兴奋。

从标记开始

button.button(style=`--hue: ${Math.random() * 360}`) Show me attention

一个简单的浮动动画可能看起来像这样

@keyframes flow {
  0%, 100% {
    transform: translate(0, 0);
  }
  50% {
    transform: translate(0, -25%);
  }
}

这将给我们类似于下面的东西

我添加了一点阴影作为补充,但这不是必要的。👍

让我们让按钮在我们悬停在其上时变得兴奋。现在,我们可以简单地将正在使用的动画更改为类似于以下内容

.button:hover {
  animation: shake .1s infinite ease-in-out;
}

@keyframes shake {
  0%, 100% {
    transform: translate(0, 0) rotate(0deg);
  }
  25% {
    transform: translate(-1%, 3%) rotate(-2deg);
  }
  50% {
    transform: translate(1%, 2%) rotate(2deg);
  }
  75% {
    transform: translate(1%, -2%) rotate(-1deg);
  }
}

它可以工作

但是,我们需要引入另一个关键帧定义。如果我们能将这两个动画合并成一个呢?就结构而言,它们并不相差太多。

我们可以尝试

@keyframes flow-and-shake {
  0%, 100% {
    transform: translate(0, 0) rotate(0deg);
  }
  25%, 75% {
    transform: translate(0, -12.5%) rotate(0deg);
  }
  50% {
    transform: translate(0, -25%) rotate(0deg);
  }
}

虽然这可以工作,但由于平移步骤,我们最终得到一个不太平滑的动画。那么我们还能做什么?让我们通过删除 25% 和 75% 处的步骤来找到折衷方案。

@keyframes flow-and-shake {
  0%, 100% {
    transform: translate(0, 0) rotate(0deg);
  }
  50% {
    transform: translate(0, -25%) rotate(0deg);
  }
}

它按预期工作,但现在让我们用一些变量来更新我们的按钮。

.button {
  --y: -25;
  --x: 0;
  --rotation: 0;
  --speed: 2;
}

现在让我们将它们插入动画定义中,以及按钮的动画属性。

.button {
  animation-name: flow-and-shake;
  animation-duration: calc(var(--speed) * 1s);
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
}

@keyframes flow-and-shake {
  0%, 100% {
    transform: translate(calc(var(--x) * -1%), calc(var(--y) * -1%))
      rotate(calc(var(--rotation) * -1deg));
  }
  50% {
    transform: translate(calc(var(--x) * 1%), calc(var(--y) * 1%))
      rotate(calc(var(--rotation) * 1deg));
  }
}

一切正常。👍

让我们在悬停按钮时更改这些值

.button:hover {
  --speed: .1;
  --x: 1;
  --y: -1;
  --rotation: -1;
}

不错!现在我们的按钮具有两种不同的动画类型,但它们通过一组关键帧定义。🤯

让我们玩得更开心一点。如果我们更进一步,我们可以让按钮更具趣味性,并且可能在它处于活动状态时完全停止动画。😅

示例 2:气泡

现在我们已经了解了一些使用范围功能可以实现的不同技巧,让我们将它们全部整合在一起。我们将创建一个随机生成的气泡场景,该场景大量利用范围 CSS 变量。

让我们从创建一个气泡开始。一个静态气泡。

.bubble {
  background: radial-gradient(100% 115% at 25% 25%, #fff, transparent 33%),
    radial-gradient(15% 15% at 75% 75%, #80dfff, transparent),
    radial-gradient(100% 100% at 50% 25%, transparent, #66d9ff 98%);
  border: 1px solid #b3ecff;
  border-radius: 100%;
  height: 50px;
  width: 50px;
}

我们使用 background 以及多个值和一个 border 来创建气泡效果——但这并不是很动态。我们知道 border-radius 将始终保持相同。我们还知道 borderbackground 的结构不会改变。但是,这些属性中使用的值以及其他属性值都可以是随机的。

让我们重构 CSS 以使用变量

.bubble {
  --size: 50;
  --hue: 195;
  --bubble-outline: hsl(var(--hue), 100%, 50%);
  --bubble-spot: hsl(var(--hue), 100%, 75%);
  --bubble-shade: hsl(var(--hue), 100%, 70%);
  background: radial-gradient(100% 115% at 25% 25%, #fff, transparent 33%),
    radial-gradient(15% 15% at 75% 75%, var(--bubble-spot), transparent),
    radial-gradient(100% 100% at 50% 25%, transparent, var(--bubble-shade) 98%);
  border: 1px solid var(--bubble-outline);
  border-radius: 100%;
  height: calc(var(--size) * 1px);
  width: calc(var(--size) * 1px);
}

这是一个很好的开始。👍

让我们添加更多气泡,并利用内联范围来定位和调整它们的大小。由于我们将开始随机化多个值,因此使用一个函数来为我们的标记生成范围内的随机数非常方便。

- const randomInRange = (max, min) => Math.floor(Math.random() * (max - min + 1)) + min

使用 Pug,我们可以利用迭代来创建一大组气泡

- const baseHue = randomInRange(0, 360)
- const bubbleCount = 50
- let b = 0
while b < bubbleCount
  - const size = randomInRange(10, 50)
  - const x = randomInRange(0, 100)
  .bubble(style=`--x: ${x}; --size: ${size}; --hue: ${baseHue}`)
  - b++

更新我们的 .bubble 样式允许我们利用新的内联变量。

.bubble {
  left: calc(var(--x) * 1%);
  position: absolute;
  transform: translate(-50%, 0);
}

这将给我们一组随机的气泡

让我们更进一步,为这些气泡设置动画,使其从上到下浮动并淡出。

.bubble {
  animation: float 5s infinite ease-in-out;
  top: 100%;
}

@keyframes float {
  from {
    opacity: 1;
    transform: translate(0, 0) scale(0);
  }
  to {
    opacity: 0;
    transform: translate(0, -100vh) scale(1);
  }
}

这太无聊了。它们都在同一时间做同样的事情。因此,让我们随机化每个气泡的移动速度、延迟、结束缩放比例和移动距离。

- const randomInRange = (max, min) => Math.floor(Math.random() * (max - min + 1)) + min
- const baseHue = randomInRange(0, 360)
- const bubbleCount = 50
- let b = 0
while b < bubbleCount
  - const size = randomInRange(10, 50)
  - const delay = randomInRange(1, 10)
  - const speed = randomInRange(2, 20)
  - const distance = randomInRange(25, 150)
  - const scale = randomInRange(100, 150) / 100
  - const x = randomInRange(0, 100)
  .bubble(style=`--x: ${x}; --size: ${size}; --hue: ${baseHue}; --distance: ${distance}; --speed: ${speed}; --delay: ${delay}; --scale: ${scale}`)
  - b++

现在,让我们更新我们的样式

.bubble {
  animation-name: float;
  animation-duration: calc(var(--speed) * 1s);
  animation-delay: calc(var(--delay) * -1s);
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
}

@keyframes float {
  from {
    opacity: 1;
    transform: translate(-50%, 0) scale(0);
  }
  to {
    opacity: 0;
    transform: translate(-50%, calc(var(--distance) * -1vh)) scale(var(--scale));
  }
}

然后我们将得到以下结果

通过大约 50 行代码,您可以通过利用范围的力量来创建一个随机生成的有动画场景!💪

就是这样!

通过充分利用 CSS 变量并利用一些小技巧,我们可以用很少的代码创建一些非常酷的东西。

我希望这篇文章能提高大家对 CSS 变量范围功能的认识,也希望大家能够充分利用它并将其传递下去 😎

这篇文章中的所有演示都在 这个 CodePen 集合 中。