探索@property及其动画力量

通过 Cloudways 获取经济实惠且轻松无忧的 WordPress 托管计划 —立即开始免费试用

呃,什么@property?这是 CSS 的新功能!它赋予你超能力。说真的,@propertyCSS 中的一些功能可以解锁我们以前无法实现的功能。

虽然一切都令人兴奋,但也许最有趣的是它提供了一种为自定义 CSS 属性指定类型的@property方法。类型为浏览器提供了更多上下文信息,这带来了一些很酷的效果:我们可以为浏览器提供过渡和动画这些属性所需的信息!

但在我们对此感到兴奋之前,值得注意的是,目前支持情况尚不完善。截至撰写本文时,@propertyChrome 和 Edge 均支持此功能。我们需要密切关注浏览器支持情况,以便能够在其他浏览器(例如 Firefox 和 Safari)上使用此功能。

首先,我们进行类型检查

@property --spinAngle {
  /* An initial value for our custom property */
  initial-value: 0deg;
  /* Whether it inherits from parent set values or not */
  inherits: false;
  /* The type. Yes, the type. You thought TypeScript was cool */
  syntax: '<angle>';
}

@keyframes spin {
  to {
    --spinAngle: 360deg;
  }
}

没错!CSS 中的类型检查。这有点像创建我们自己的迷你 CSS 规范。这是一个简单的例子。来看看我们可以使用的各种类型:

  • length
  • number
  • percentage
  • length-percentage
  • color
  • image
  • url
  • integer
  • angle
  • time
  • resolution
  • transform-list
  • transform-function
  • custom-ident(自定义标识符字符串)

在此之前,我们可能依赖于使用“技巧”来为具有自定义属性的动画提供动力。

那么我们能做什么酷炫的事情呢?让我们一起来看看,激发我们的想象力。

让我们为色彩注入活力

如何通过一系列颜色或它们之间的变化来为元素添加动画效果?我非常推崇 HSL 色彩空间,它将元素分解成易于理解的数字:色相、饱和度和亮度。

为色调添加动画效果感觉很有趣。什么东西色彩缤纷?彩虹!我们有很多方法可以制作彩虹。以下是其中一种:

 

在此示例中,我们为彩虹的不同色带设置了 CSS 自定义属性,以便将:nth-child()其限定在各个色带内。每个色带也都设置了--index相应的属性,用于调整大小。

为了使这些带子动起来,我们可能会使用它--index来设置一些负动画延迟,然后使用相同的关键帧动画来循环色调。

.rainbow__band {
  border-color: hsl(var(--hue, 10), 80%, 50%);
  animation: rainbow 2s calc(var(--index, 0) * -0.2s) infinite linear;
}

@keyframes rainbow {
  0%, 100% {
    --hue: 10;
  }
  14% {
    --hue: 35;
  }
  28% {
    --hue: 55;
  }
  42% {
    --hue: 110;
  }
  56% {
    --hue: 200;
  }
  70% {
    --hue: 230;
  }
  84% {
    --hue: 280;
  }
}

如果你想要“阶梯式”效果,这或许可以。但是,这些关键帧步长并不是特别精确。我使用步长来14%粗略地跳跃。

 

我们可以给它添加动画border-color,这样就能完成任务了。但是,我们仍然会遇到关键帧步长的计算问题。而且我们需要编写大量的 CSS 代码才能完成这个任务:

@keyframes rainbow {
  0%, 100% {
    border-color: hsl(10, 80%, 50%);
  }
  14% {
    border-color: hsl(35, 80%, 50%);
  }
  28% {
    border-color: hsl(55, 80%, 50%);
  }
  42% {
    border-color: hsl(110, 80%, 50%);
  }
  56% {
    border-color: hsl(200, 80%, 50%);
  }
  70% {
    border-color: hsl(230, 80%, 50%);
  }
  84% {
    border-color: hsl(280, 80%, 50%);
  }
}

输入@property。我们先为 hue 定义一个自定义属性。这会告诉浏览器我们的自定义属性--hue是一个数字(而不是看起来像数字的字符串):

@property --hue {
  initial-value: 0;
  inherits: false;
  syntax: '<number>';
}

HSL 中的色调值范围从0360。我们的初始值为0。该值不会继承。在本例中,我们的值是一个数字。动画效果非常简单:

@keyframes rainbow {
  to {
    --hue: 360;
  }
}

是的,就是这个票:

 

为了确保起始点准确,我们可以为每个频段添加延迟。这能给我们带来一些很棒的灵活性。例如,我们可以增加延迟,animation-duration这样就能得到一个慢速循环。在这个演示中,你可以尝试一下这个速度。

 

这或许不是最“疯狂”的例子,但我认为,当我们使用那些能够逻辑运用数字的色彩空间时,色彩动画会有一些有趣的机会。之前通过色轮制作动画需要一些技巧。例如,使用预处理器(例如 Stylus)生成关键帧:

@keyframes party 
  for $frame in (0..100)
    {$frame * 1%}
      background 'hsl(%s, 65%, 40%)' % ($frame * 3.6)

我们这样做纯粹是因为浏览器无法理解这一点。浏览器会将色轮上从 0 到 360 的过渡视为瞬间过渡,因为两个 hsl 值显示的颜色相同。

@keyframes party {
  from {
    background: hsl(0, 80%, 50%); 
  }
  to {
    background: hsl(360, 80%, 50%);
  }
}

关键帧是相同的,因此浏览器假定动画保持在相同的background值,而我们真正想要的是让浏览器遍历整个色调光谱,从一个值开始,并在完成动作后以相同的值结束。

想想我们在这里拥有的所有其他机会。我们可以:

  • 动画饱和度
  • 使用不同的缓和措施
  • 赋予光明生命力
  • 尝试rgb()
  • 尝试度hsl()并将我们的自定义属性类型声明为<angle>

巧妙的是,我们可以通过作用域在元素之间共享动画值!想想这个按钮。鼠标悬停时,边框和阴影会通过色轮进行动画。

 

动画色彩让我想到......哇!

 

直接编号

因为我们可以定义数字的类型——比如integernumber——这意味着我们可以为数字添加动画效果,而不是将这些数字作为其他内容的一部分。Carter Li实际上在 CSS-Tricks 上写了一篇关于此的文章。诀窍是将integer和 CSS 计数器结合使用。这类似于我们在类似这个“纯 CSS”游戏中使用计数器的方式。

使用counter和 伪元素可以将数字转换为字符串。然后,我们可以将该字符串用作 的content伪元素。以下是一些要点:

@property --milliseconds {
  inherits: false;
  initial-value: 0;
  syntax: '<integer>';
}

.counter {
  counter-reset: ms var(--milliseconds);
  animation: count 1s steps(100) infinite;
}

.counter:after {
  content: counter(ms);
}

@keyframes count {
  to {
    --milliseconds: 100;
  }
}

这给了我们类似这样的结果。很酷。

 

进一步,你就拥有了一个可以工作的秒表,只用 CSS 和 HTML 就能实现。点击按钮!最棒的是,它真的可以作为计时器使用。它不会出现时间漂移。在某些方面,它可能比我们常用的 JavaScript 解决方案(例如)更精确setInterval。观看 Google Chrome 开发者提供的这段关于 JavaScript 计数器的精彩视频

 

你还能用动画数字做什么?比如倒计时?

动画渐变

你知道线性、径向和圆锥曲线。你有没有想过要给颜色停止点添加过渡或动画效果?好吧,@property可以!

想象一下,我们在海滩上创建了一些波浪的渐变。一旦我们将一些图像叠加在一起,我们就可以制作出类似这样的效果。

body {
  background-image:
    linear-gradient(transparent 0 calc(35% + (var(--wave) * 0.5)), var(--wave-four) calc(75% + var(--wave)) 100%),
    linear-gradient(transparent 0 calc(35% + (var(--wave) * 0.5)), var(--wave-three) calc(50% + var(--wave)) calc(75% + var(--wave))),
    linear-gradient(transparent 0 calc(20% + (var(--wave) * 0.5)), var(--wave-two) calc(35% + var(--wave)) calc(50% + var(--wave))),
    linear-gradient(transparent 0 calc(15% + (var(--wave) * 0.5)), var(--wave-one) calc(25% + var(--wave)) calc(35% + var(--wave))), var(--sand);
}

这里面有很多事情要做。不过,分解一下,我们用 来创建每个色标calc()。在计算过程中,我们加上 的值--wave。这里的巧妙之处在于,当我们为该--wave值设置动画时,所有波浪层都会移动。

 

这就是实现这一目标所需的全部代码:

body {
  animation: waves 5s infinite ease-in-out;
}
@keyframes waves {
  50% {
    --wave: 25%;
  }
}

如果不使用@property,我们的波浪会在涨潮和退潮之间变化。但是,有了它,我们就能得到像这样的凉爽效果。

 

想想我们在处理图像时还能遇到的其他巧妙机会,真是令人兴奋。比如旋转。或者如何为某个角度制作动画conic-gradient……但是,在一个……之内border-image。布拉姆斯·范·达姆(Bramus Van Damme)出色地诠释了这个概念

让我们通过创建一个充电指示器来分解它。我们将同时为角度和色调设置动画。我们可以从两个自定义属性开始:

@property --angle {
  initial-value: 0deg;
  inherits: false;
  syntax: '<number>';
}

@property --hue {
  initial-value: 0;
  inherits: false;
  syntax: '<angle>';
}

动画将在每次迭代时稍作停顿来更新角度和色调。

@keyframes load {
  0%, 10% {
    --angle: 0deg;
    --hue: 0;
  }
  100% {
    --angle: 360deg;
    --hue: 100;
  }
}

现在让我们将其作为border-image元素的应用。

.loader {
  --charge: hsl(var(--hue), 80%, 50%);
  border-image: conic-gradient(var(--charge) var(--angle), transparent calc(var(--angle) * 0.5deg)) 30;
  animation: load 2s infinite ease-in-out;
}

很酷。

 

可惜的是,border-image它和 效果不太好border-radius。不过,我们可以在它后面加一个伪元素。把它和之前的数字动画技巧结合起来,我们就得到了一个完整的充电/加载动画。(没错,当达到 100% 时,它会发生变化。)

 

变形也很酷

动画变换的一个问题是特定部分之间的过渡。它经常会中断或看起来不正常。考虑一个经典的例子:一个球被抛出。我们希望它从 A 点移动到 B 点,同时模拟重力效应。

初步尝试可能看起来像这样

@keyframes throw {
  0% {
    transform: translate(-500%, 0);
  }
  50% {
    transform: translate(0, -250%);
  }
  100% {
    transform: translate(500%, 0);
  }
}

但是,我们很快就会发现它并不像我们想要的那样。

 

以前,我们可能使用过包装元素,并单独为它们添加动画。但是,有了@property,我们可以为变换的各个值添加动画。而且所有这些都在一条时间轴上完成。让我们通过定义自定义属性,然后在球上设置变换来改变这种工作方式。

@property --x {
  inherits: false;
  initial-value: 0%;
  syntax: '<percentage>';
}

@property --y {
  inherits: false;
  initial-value: 0%;
  syntax: '<percentage>';
}

@property --rotate {
  inherits: false;
  initial-value: 0deg;
  syntax: '<angle>';
}

.ball {
  animation: throw 1s infinite alternate ease-in-out;
  transform: translateX(var(--x)) translateY(var(--y)) rotate(var(--rotate));
}

现在对于我们的动画,我们可以针对关键帧组合我们想要的变换:

@keyframes throw {
  0% {
    --x: -500%;
    --rotate: 0deg;
  }
  50% {
    --y: -250%;
  }
  100% {
    --x: 500%;
    --rotate: 360deg;
  }
}

结果如何?我们期望的曲线路径。而且,我们可以根据所使用的不同计时函数,让它看起来有所不同。我们可以将动画分成三部分,并使用不同的计时函数。这样,球的移动方式就会呈现出不同的效果。

 

考虑另一个例子,我们有一辆汽车,我们想让它绕着一个圆角的正方形行驶。

 

我们可以使用与处理球类似的方法:

@property --x {
  inherits: false;
  initial-value: -22.5;
  syntax: '<number>';
}

@property --y {
  inherits: false;
  initial-value: 0;
  syntax: '<number>';
}

@property --r {
  inherits: false;
  initial-value: 0deg;
  syntax: '<angle>';
}

汽车transform正在使用以下计算来vmin保持响应:

.car {
  transform: translate(calc(var(--x) * 1vmin), calc(var(--y) * 1vmin)) rotate(var(--r));
}

现在可以为汽车编写极其精确的逐帧旅程。我们可以从 的值开始--x

@keyframes journey {
  0%, 100% {
    --x: -22.5;
  }
  25% {
    --x: 0;
  }
  50% {
    --x: 22.5;
  }
  75% {
    --x: 0;
  }
}

汽车在 x 轴上正确行驶。

 

然后我们在此基础上添加 y 轴的行程:

@keyframes journey {
  0%, 100% {
    --x: -22.5;
    --y: 0;
  }
  25% {
    --x: 0;
    --y: -22.5;
  }
  50% {
    --x: 22.5;
    --y: 0;
  }
  75% {
    --x: 0;
    --y: 22.5;
  }
}

嗯,这并不完全正确。

 

让我们增加一些额外的步骤来@keyframes让事情顺利进行:

@keyframes journey {
  0%, 100% {
    --x: -22.5;
    --y: 0;
  }
  12.5% {
    --x: -22.5;
    --y: -22.5;
  }
  25% {
    --x: 0;
    --y: -22.5;
  }
  37.5% {
    --y: -22.5;
    --x: 22.5;
  }
  50% {
    --x: 22.5;
    --y: 0;
  }
  62.5% {
    --x: 22.5;
    --y: 22.5;
  }
  75% {
    --x: 0;
    --y: 22.5;
  }
  87.5% {
    --x: -22.5;
    --y: 22.5;
  }
}

啊,现在好多了:

 

剩下的就是车辆的旋转了。我们在转弯处设置了 5% 的窗口。虽然不是很精确,但它确实展现了车辆的潜力:

@keyframes journey {
  0% {
    --x: -22.5;
    --y: 0;
    --r: 0deg;
  }
  10% {
    --r: 0deg;
  }
  12.5% {
    --x: -22.5;
    --y: -22.5;
  }
  15% {
    --r: 90deg;
  }
  25% {
    --x: 0;
    --y: -22.5;
  }
  35% {
    --r: 90deg;
  }
  37.5% {
    --y: -22.5;
    --x: 22.5;
  }
  40% {
    --r: 180deg;
  }
  50% {
    --x: 22.5;
    --y: 0;
  }
  60% {
    --r: 180deg;
  }
  62.5% {
    --x: 22.5;
    --y: 22.5;
  }
  65% {
    --r: 270deg;
  }
  75% {
    --x: 0;
    --y: 22.5;
  }
  85% {
    --r: 270deg;
  }
  87.5% {
    --x: -22.5;
    --y: 22.5;
  }
  90% {
    --r: 360deg;
  }
  100% {
    --x: -22.5;
    --y: 0;
    --r: 360deg;
  }
}

就这样,一辆汽车绕着弯曲的正方形行驶!无需包装器,无需复杂的数学运算。我们用自定义属性完成了这一切。

 

使用变量来驱动整个场景

到目前为止,我们已经看到了一些相当巧妙的@property可能性,但将所有这些结合起来,可以提升到一个新的高度。例如,我们只需几个自定义属性就可以为整个场景提供支持。

考虑以下 404 页面的概念。两个已注册的属性驱动不同的移动部分。我们有一个使用 裁剪的移动渐变-webkit-background-clip。阴影通过读取属性值来移动。我们摆动另一个元素来实现灯光效果。

 

就是这样!

想想我们能用 定义类型来做哪些事情,真是令人兴奋@property。通过为浏览器提供有关自定义属性的额外上下文,我们可以实现以前使用基本字符串无法实现的功能。

你对其他类型有什么想法?时间和分辨率可以带来有趣的过渡效果,虽然我承认我没能像我希望的那样实现。url也可以做得更简洁,比如像图片轮播那样在一系列来源之间过渡。只是集思广益而已!

希望这篇快速介绍@property能激发你的灵感,让你去尝试并制作出属于自己的精彩演示!我期待看到你的成果。欢迎在评论区分享!

探索@property及其动画功能| CSS-Tricks

posted @ 2025-08-14 09:27  CharyGao  阅读(5)  评论(0)    收藏  举报