You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

CSS关键帧动画速度调整:滚动列表动画序列冲突问题求解

问题分析与解决方案

一、问题本质:动画时序与元素层级冲突

你遇到的核心问题可以拆解成两点:

  1. 元素层级重叠:所有.element都设置了grid-column:1/-1; grid-row:1/-1;,意味着它们都挤在网格的同一个单元格里。默认情况下DOM顺序靠后的元素层级更高,会直接覆盖前面的元素——当后面的元素动画启动时,哪怕前面的元素动画还没结束,也会被层级更高的新元素盖过去,视觉上就像“被打断”。
  2. 动画时序不匹配:你的roll动画时长是35s,相邻元素的延迟间隔却只有5s,这就导致第2个元素在第5s启动时,第1个元素的动画还在进行(要35s才结束),但此时第2个元素层级更高,直接覆盖了前者,自然会出现“打断”的错觉。

二、原理拆解

1. 网格布局下的元素堆叠规则

当多个元素占据网格同一单元格时,它们的堆叠顺序遵循CSS层叠上下文逻辑:

  • 默认状态下,DOM中后出现的元素(.element:nth-child(n)里n更大的)堆叠层级更高,z-index:auto时,后序元素会覆盖前序元素。
  • 除非手动设置z-index调整层级,或者通过动画关键帧动态控制元素的可见性/层级。

2. 动画时序的逻辑矛盾

你的动画参数存在明显的时序冲突:

  • 每个元素的animation-delay依次增加5s,但动画时长是35s且设置了infinite循环
  • 第1个元素1s启动,36s完成第一轮;第2个5s启动,40s完成第一轮……当第2个元素启动时,第1个元素还在动画过程中,但因为层级更高,直接被覆盖,所以你看不到第1个元素的后续动画。

三、可行解决方案

我们从层级控制时序匹配两个方向调整,这里提供两种实用方案:

方案1:时序对齐+动态层级调整

核心思路:让总动画时长等于所有元素的延迟周期总和,同时在动画关键帧中动态调整元素的z-index,确保当前显示的元素层级最高。

/* 先修改roll动画,加入z-index的关键帧控制 */
@keyframes roll {
  0% {
    opacity: 0;
    z-index: 1; /* 初始层级压低 */
    /* 这里添加你的初始动画属性,比如transform: translateY(100%); */
  }
  14.28% { /* 1/7≈14.28%,对应7个元素的单个显示占比 */
    opacity: 1;
    z-index: 10; /* 显示时设为最高层级 */
    /* 这里添加动画中间状态,比如transform: translateY(0); */
  }
  85.72% {
    opacity: 1;
    z-index: 10; /* 保持显示层级直到即将结束 */
  }
  100% {
    opacity: 0;
    z-index: 1; /* 结束后层级压低 */
    /* 这里添加动画结束状态,比如transform: translateY(-100%); */
  }
}

/* 调整元素的动画参数 */
.element {
  grid-column: 1/-1;
  grid-row: 1/-1;
  margin-right: auto;
  /* 总时长=元素数量×间隔时长,7×5=35s,完美匹配延迟周期 */
  animation: roll 35s cubic-bezier(.25,.1,.25,1) infinite backwards;
}

/* 延迟设置保持5s间隔,第1个元素从0s开始 */
.element:nth-child(1) { animation-delay: 0s; }
.element:nth-child(2) { animation-delay: 5s; }
.element:nth-child(3) { animation-delay: 10s; }
.element:nth-child(4) { animation-delay: 15s; }
.element:nth-child(5) { animation-delay: 20s; }
.element:nth-child(6) { animation-delay: 25s; }
.element:nth-child(7) { animation-delay: 30s; }

原理:

  • 总动画时长35s刚好覆盖7个元素的延迟周期,循环时不会出现时序混乱。
  • 只有元素处于“显示阶段”时才设置最高层级,其他时候层级压低,彻底避免覆盖问题。

方案2:利用可见性控制替代层级调整

如果不想调整z-index,可以在动画中控制元素的visibility,确保只有当前动画的元素可见,其他元素隐藏:

@keyframes roll {
  0% {
    opacity: 0;
    visibility: hidden;
    /* 初始动画状态 */
  }
  2.85% { /* 留一点缓冲避免闪烁,对应5s显示时长在35s总时长里的起始占比 */
    opacity: 1;
    visibility: visible;
    /* 动画中间显示状态 */
  }
  17.13% {
    opacity: 1;
    visibility: visible;
  }
  17.14% {
    opacity: 0;
    visibility: hidden;
    /* 动画结束隐藏状态 */
  }
  100% {
    opacity: 0;
    visibility: hidden;
  }
}

.element {
  grid-column: 1/-1;
  grid-row: 1/-1;
  margin-right: auto;
  animation: roll 35s cubic-bezier(.25,.1,.25,1) infinite backwards;
}

/* 延迟设置保持不变 */
.element:nth-child(1) { animation-delay: 0s; }
.element:nth-child(2) { animation-delay: 5s; }
.element:nth-child(3) { animation-delay: 10s; }
.element:nth-child(4) { animation-delay: 15s; }
.element:nth-child(5) { animation-delay: 20s; }
.element:nth-child(6) { animation-delay: 25s; }
.element:nth-child(7) { animation-delay: 30s; }

原理:

  • visibility:hidden不同于display:none,不会改变布局,但会让元素完全不可见,哪怕层级高也不会显示出来。
  • 关键帧的百分比计算:单个元素显示5s,总动画时长35s,所以显示区间占比约14.28%,设置前后缓冲百分比避免动画切换时的闪烁。

四、额外优化提示

可以用CSS变量简化延迟设置,后期修改元素数量或间隔时间会更方便:

.element {
  --delay: calc(var(--index) * 5s);
  animation-delay: var(--delay);
}
/* 给每个元素设置索引变量 */
.element:nth-child(1) { --index: 0; }
.element:nth-child(2) { --index: 1; }
.element:nth-child(3) { --index: 2; }
.element:nth-child(4) { --index: 3; }
.element:nth-child(5) { --index: 4; }
.element:nth-child(6) { --index: 5; }
.element:nth-child(7) { --index: 6; }

内容的提问来源于stack exchange,提问作者Jay Gray

火山引擎 最新活动