使用秒而非百分比表达CSS3 @keyframes

12

我在2016年1月学习了CSS3的@keyframes语法,并且两年多后,我现在发现自己在很多工作中都使用了@keyframes动画(比CSS3转换更复杂,比基于JavaScript的动画更简便)。

但我确实想要的是用秒来表示@keyframes而不是百分比的能力。有没有什么技巧可以实现这一点呢?

我知道我可以使用以下100s技巧循环播放彩虹色,每3秒一个周期:

div {
    width: 120px;
    height: 120px;
    background-color: violet;
    animation: myAnimation 100s;
}

@keyframes myAnimation {
    0% {background-color: red;}
    3% {background-color: orange;}
    6% {background-color: yellow;}
    9% {background-color: green;}
   12% {background-color: cyan;}
   15% {background-color: blue;}
   18% {background-color: violet;}
  100% {background-color: violet;}
}
<div></div>

但这意味着动画在完成后仍然运行了另外82秒(虽然几乎不可察觉)。除了其他问题之外,这也导致多次迭代无法达成。

实际上,我想写的是:

@keyframes myAnimation {

  0s {background-color: red;}
  3s {background-color: orange;}
  6s {background-color: yellow;}
  9s {background-color: green;}
 12s {background-color: cyan;}
 15s {background-color: blue;}
 18s {background-color: violet;}
}

有没有比我在上面代码框中详细介绍的方法更好的方法?


使用多个元素的示例

事后我意识到,上面的示例可能太简单了,因为它只涉及一个元素的动画,而我的问题最初源于想要将多个元素同步动画。

因此,这里是一个稍微复杂一些的示例,展示了一个更接近引起我问题的设置:

div {
display: inline-block;
width: 48px;
height: 48px;
margin-right: 6px;
}

div:nth-of-type(1) {
background-color: red;
}

div:nth-of-type(2) {
background-color: orange;
animation: myAnimationOrange 100s;
}

div:nth-of-type(3) {
background-color: yellow;
animation: myAnimationYellow 100s;
}

div:nth-of-type(4) {
background-color: green;
animation: myAnimationGreen 100s;
}

div:nth-of-type(5) {
background-color: cyan;
animation: myAnimationCyan 100s;
}

div:nth-of-type(6) {
background-color: violet;
animation: myAnimationViolet 100s;
}

@keyframes myAnimationOrange {
    0% {background-color: white;}
    1% {background-color: white;}
    2% {background-color: orange;}
  100% {background-color: orange;}
}

@keyframes myAnimationYellow {
    0% {background-color: white;}
    2% {background-color: white;}
    3% {background-color: yellow;}
  100% {background-color: yellow;}
}

@keyframes myAnimationGreen {
    0% {background-color: white;}
    3% {background-color: white;}
    4% {background-color: green;}
  100% {background-color: green;}
}

@keyframes myAnimationCyan {
    0% {background-color: white;}
    4% {background-color: white;}
    5% {background-color: cyan;}
  100% {background-color: cyan;}
}

@keyframes myAnimationViolet {
    0% {background-color: white;}
    5% {background-color: white;}
    6% {background-color: violet;}
  100% {background-color: violet;}
}
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>


我不确定我理解您的观点,使用动画持续时间和百分比,您基本上正在定义每个关键帧将持续多少秒。如果使用秒符号,这意味着每个关键帧块将具有固定的持续时间,但百分比则不是这种情况。 - Mladen Ilić
1
就像你想限制CSS3动画的功能一样...如果我们指定了秒数,那么timing-function和duration将变得无用。 - Temani Afif
我不明白添加单位如何会有限制,@Temani。我可以用pxvw(它们的工作方式不同)或(又有不同的工作方式)来表示“宽度”。这些中的任何一个都不会限制其他的。 - Rounin
@Rounin如果你在宽度上使用px,那么它是静态的,不会改变,因此受限,不像使用宽度%时,它是动态的,可以响应等等...同样的事情也发生在动画中,当你指定%时,你可以稍后使用这个动画与任何元素一起,通过指定持续时间、延迟和计时函数使其变得动态。但是,如果你将动画设置为固定的秒数,它将是静态的,你不能再使用不同的持续时间、延迟等,因为你把所有东西都固定了。所以你把你的动画限制在只有一个用例中,不像使用%时。 - Temani Afif
1
@MladenIlic - 是的,但是假设在 20 秒的时间段内,我希望有 20 件不同的事情发生。所以这是一个持续时间为 20s 的过程,并且我使用 5%10% 来标记每个关键帧。现在,假设我想要添加另外 3 个项目。所以我将持续时间更新为 23s,然后...我必须浏览所有的百分比并将 5% 更改为 4.345%10% 更改为 8.69%15% 更改为 13.035% 等。 - Rounin
@TemaniAfif - 使用像秒和像素这样的硬单位,以及百分比和视口宽度单位等动态单位并不会限制。基本上,能够使用任何不同的、额外的单位(无论是硬单位还是动态单位)都不会受到限制。你可以说硬单位比动态单位更具限制性,我会同意你的观点。但是,能够同时使用硬单位和动态单位并不是某种约束。 - Rounin
4个回答

27

不要忘记同一个元素上可以运行多个动画,并且您可以独立设置它们的durationdelay和所有其他animation-...规则。

例如,您可以将所有关键帧拆分为单个@keyframes规则。
然后就很容易控制它们何时开始并链接它们。

div {
    width: 120px;
    height: 120px;
    background-color: violet;
    animation-fill-mode: forwards;
    animation-name: orange, yellow, green, cyan, blue, violet;
    animation-delay: 0s, 3s, 6s, 9s, 12s, 15s, 18s;
    animation-duration: 3s; /* same for all */
}

@keyframes orange {
    to { background-color: orange; }
}
@keyframes yellow {
    to { background-color: yellow; }
}
@keyframes green {
    to { background-color: green; }
}
@keyframes cyan {
    to { background-color: cyan; }
}
@keyframes blue {
    to { background-color: blue; }
}
@keyframes violet {
    to { background-color: violet; }
}
<div></div>

关于问题的编辑

在这种情况下,您甚至不需要将多个动画组合在同一元素上,只需相应地设置animation-delay即可:

div {
 /* same for all */
    width: 60px;
    height: 60px;
    display: inline-block;
    background-color: white;
    animation-fill-mode: forwards;
    animation-duration: 3s;
}
div:nth-of-type(1) {
  animation-name: orange;
  animation-delay: 0s;
}
div:nth-of-type(2) {
  animation-name: yellow;
  animation-delay: 3s;
}
div:nth-of-type(3) {
  animation-name: green;
  animation-delay: 6s;
}
div:nth-of-type(4) {
  animation-name: cyan;
  animation-delay: 9s;
}
div:nth-of-type(5) {
  animation-name: blue;
  animation-delay: 12s;
}
div:nth-of-type(6) {
  animation-name: violet;
  animation-delay: 15s;
}

@keyframes orange {
    to { background-color: orange; }
}
@keyframes yellow {
    to { background-color: yellow; }
}
@keyframes green {
    to { background-color: green; }
}
@keyframes cyan {
    to { background-color: cyan; }
}
@keyframes blue {
    to { background-color: blue; }
}
@keyframes violet {
    to { background-color: violet; }
}
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>

但是如果您想将两者结合在一起,也完全可以:

div {
 /* same for all */
    width: 60px;
    height: 60px;
    display: inline-block;
    background-color: white;
    animation-fill-mode: forwards;
    animation-duration: 3s;
}
div:nth-of-type(1) {
  animation-name: orange, yellow, green, cyan, blue, violet;
  animation-delay: 0s, 3s, 6s, 9s, 12s, 15s;
}
div:nth-of-type(2) {
  animation-name: yellow, green, cyan, blue, violet;
  animation-delay: 3s, 6s, 9s, 12s, 15s;
}
div:nth-of-type(3) {
  animation-name: green, cyan, blue, violet;
  animation-delay: 6s, 9s, 12s, 15s;
}
div:nth-of-type(4) {
  animation-name: cyan, blue, violet;
  animation-delay: 9s, 12s, 15s;
}
div:nth-of-type(5) {
  animation-name: blue, violet;
  animation-delay: 12s, 15s;
}
div:nth-of-type(6) {
  animation-name: violet;
  animation-delay: 15s;
}

@keyframes orange {
    to { background-color: orange; }
}
@keyframes yellow {
    to { background-color: yellow; }
}
@keyframes green {
    to { background-color: green; }
}
@keyframes cyan {
    to { background-color: cyan; }
}
@keyframes blue {
    to { background-color: blue; }
}
@keyframes violet {
    to { background-color: violet; }
}
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>


谢谢,@kaiido,我非常喜欢这个。如果我找不到更好的解决方案,我会将其标记为最佳答案。我真正想做的是在多个元素上应用单个动画(而不是在单个元素上应用多个动画)。我意识到我在问题中提供的原始示例过于简单,所以我已经更新了它以更好地解释我遇到的问题。 - Rounin
2
请注意,只有在animation-iteration-count为1时才能正常工作,因为delays仅限于初始偏移量。 - Jakob E

3

SCSS混合器将延迟转换为百分比

示例 https://codepen.io/jakob-e/pen/vVXeOe

SCSS

body {
    @include animation-timeline {
        animation-iteration-count: infinite;
        animation-fill-mode: forwards;
        
        @include tween(0s, (background-color: violet));        
        @include tween(3s, (background-color: red));
        @include tween(3s, (background-color: orange));
        @include tween(3s, (background-color: yellow));
        @include tween(3s, (background-color: green));
        @include tween(3s, (background-color: cyan));
        @include tween(3s, (background-color: blue));
        @include tween(3s, (background-color: violet));
    }
}

CSS 输出

body {
    animation-iteration-count: infinite;
    animation-fill-mode: forwards;
    animation-name: u33q65otn;
    animation-duration: 21s;
}

@keyframes u33q65otn {
    0% { background-color: violet; }
    14.2857142857% { background-color: red; }
    28.5714285714% { background-color: orange; }
    42.8571428571% { background-color: yellow; }
    57.1428571429% { background-color: green; }
    71.4285714286% { background-color: cyan; }
    85.7142857143% { background-color: blue; }
    100% { background-color: violet; }
}

混合(mixins)

//  global context flags using wierd ☠️ names to minimize 
//  chance of naming conflicts with other scss variables
$☠️--animation-timeline-duration: null;
$☠️--animation-timeline-tweens  : null;

//  mixin to create an animation context for nested tweens
//  used to calculate the total duration of the animation
//  converting each tween delay into percentages 
@mixin animation-timeline($name: unique-id()) {
    
    //  global context flag to sum up duration
    $☠️--animation-timeline-duration: 0s !global;    
    
    //  global context map to hold animation tweens
    $☠️--animation-timeline-tweens  : () !global;
    
    //  mixin content (the included tweens)
    @content;

    //  animation name and duration 
    //  note! if no name is provided a unique id will be used
    //  this allows you to create one-time-use animations without
    //  having to deal with animation naming conflicts :-)     
    animation-name:     $name;
    animation-duration: $☠️--animation-timeline-duration;

    //  create keyframes
    @keyframes #{$name} {
        //  loop through the included tweens 
        @each $time, $props in $☠️--animation-timeline-tweens {
            //  calculate percentage based on total duration
            #{percentage($time/$☠️--animation-timeline-duration)}{ 
                //  print out the tween properties
                @each $prop, $value in $props {
                    #{$prop}:$value;
                }
            }
        }
    }
    
    //  reset global context flags
    $☠️--animation-timeline-duration: null !global;
    $☠️--animation-timeline-tweens  : null !global;
}


//  mixin to create tweens based on a delay and a map
//  containing the the tween properties*
//
//  * using a map is not optimal – but for now you are not 
//  able to save @content to variables :( 
//  
@mixin tween($delay: 0s, $props: null){
    //  only do stuff if we are in a animation-timeline context
    @if $☠️--animation-timeline-tweens {
        //  increment the total animation by the the tween delay 
        $☠️--animation-timeline-duration: $☠️--animation-timeline-duration + $delay !global;
        //  save current duration and tween props to the global tween map
        $☠️--animation-timeline-tweens: map-merge($☠️--animation-timeline-tweens,  ($☠️--animation-timeline-duration: $props)) !global;
    }
}

1
绝对疯狂的混合使用起来效果惊人 - 谢谢! - Brandon Wang

2
目前不行。文档明确指出,只能使用百分比:https://developer.mozilla.org/en-US/docs/Web/CSS/@keyframes。可能的原因是动画的持续时间没有在关键帧中定义,而是在animation-duration属性中定义,因此插值器必须能够将关键帧拉伸到任何持续时间。

-1

你的hack有点尴尬。为什么不直接使用18s作为动画持续时间,并稳定地循环百分比呢?

div {
   width: 120px;
   height: 120px;
   background-color: violet;
   animation: myAnimation 18s;
}

@keyframes myAnimation {
   0% {background-color: red;}
   18% {background-color: orange;}
   37% {background-color: yellow;}
   55% {background-color: green;}
   71% {background-color: cyan;}
   88% {background-color: blue;}
   100% {background-color: violet;}
}
<div></div>

我认为你不应该使用秒作为你的@keyframes间隔。在CSS中,我们已经将秒用于其他所有内容。在这里查看它是如何工作的


1
“为什么不只使用18秒的动画时长,然后循序渐进地循环百分比呢?”因为这会让更新变得非常痛苦。假设在20秒内我想要20个不同的元素进行动画。那么这个持续时间就是20秒,而我使用5%,10%来标记每个关键帧。现在,假设我想要再添加3个元素。所以我将持续时间更新为23秒,现在我必须浏览所有的百分比,将5%更改为4.345%,将10%更改为8.69%,将15%更改为13.035%等等。” - Rounin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接