CSS 动画不同步 1 帧

CSS animations out of sync by 1 frame

我有两个名为 slidebounce-return 的动画,它们都应该以 0 延迟和 1 秒持续时间播放。由于我怀疑是动画处理逻辑中的某种竞争条件,无论动画速度或刷新率如何,这些动画都不同步 1 帧。我怎样才能阻止这些圈子跳进跳出?

(如果您没有立即发现任何问题,请在整页中查看代码段或打开开发者工具。)

.items {
  --var-circle-size: 16px;
  --var-circle-space: 8px;
  --var-circle-border: solid red;
  --var-circle-border-width: 0px;
  --var-circle-shadow: 3px 2px 4px -1px rgba(0, 0, 0, 0.4);
  --var-circle-shadow-high: 6px 4px 6px -2px rgba(0, 0, 0, 0.2);
  --var-circle-count: 5;
  --var-anim-bounce-func: cubic-bezier(0.7, 0.1, 0.6, 1.6);
  --var-anim-time-unit: calc(1s);
  --var-anim-color-cycle-time: calc(var(--var-circle-count)*var(--var-anim-time-unit));
  --var-anim-color-cycle-func: steps(1);
  --var-anim-color-1: #6db;
  --var-anim-color-2: #be4;
  --var-anim-color-3: #ec5;
  --var-anim-color-4: #d86;
  --var-anim-color-5: #f8c;
  --var-anim-slide-time: var(--var-anim-time-unit);
  --var-anim-slide-func: linear;
  --var-circle-size-space: calc(var(--var-circle-size) + var(--var-circle-space));
  --var-anim-slide-amount: calc(var(--var-circle-size-space)/2);
  --var-anim-slide-start: calc(-1*var(--var-anim-slide-amount));
  --var-anim-slide-end: var(--var-anim-slide-amount);
}

.maximized {
  position: fixed;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
}

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

.items {
  font-size: 0;
  white-space: nowrap;
  user-select: none;
  pointer-events: none;
  margin-right: calc(-1*var(--var-circle-space) - var(--var-circle-size-space));
  animation: slide var(--var-anim-slide-time) var(--var-anim-slide-func) infinite;
}

.circle {
  width: var(--var-circle-size);
  height: var(--var-circle-size);
  border-radius: 100%;
  box-shadow: var(--var-circle-shadow);
  display: inline-block;
  margin: 0;
  margin-right: var(--var-circle-space);
  box-sizing: border-box;
  border: var(--var-circle-border);
  border-width: min(var(--var-circle-border-width), calc(var(--var-circle-size)/2));
  background-color: var(--var-anim-bgcolor);
  --var-color-cycle-anim: anim-color-cycle var(--var-anim-color-cycle-time) var(--var-anim-color-cycle-func) calc(var(--var-anim-delay-fact)*var(--var-anim-time-unit)) infinite;
  animation: var(--var-color-cycle-anim);
}

@keyframes anim-color-cycle {
  0% {
    --var-anim-bgcolor: var(--var-anim-color-1);
  }
  20% {
    --var-anim-bgcolor: var(--var-anim-color-2);
  }
  40% {
    --var-anim-bgcolor: var(--var-anim-color-3);
  }
  60% {
    --var-anim-bgcolor: var(--var-anim-color-4);
  }
  80% {
    --var-anim-bgcolor: var(--var-anim-color-5);
  }
  100% {
    --var-anim-bgcolor: var(--var-anim-color-1);
  }
}

@keyframes slide {
  0% {
    transform: translate(var(--var-anim-slide-start), 0);
  }
  100% {
    transform: translate(var(--var-anim-slide-end), 0);
  }
}

@keyframes bounce-inout {
  40%,
  60% {
    transform: translate(0, var(--var-anim-bounce-inout-fact));
    box-shadow: var(--var-circle-shadow-high);
  }
}

@keyframes bounce-return {
  10% {
    left: 0;
  }
  90%,
  100% {
    left: var(--var-anim-bounce-return-amount);
  }
}

@keyframes bounce-inout-fact-alternate {
  0% {
    --var-anim-bounce-inout-fact: calc(-1*var(--var-circle-size-space));
  }
  50% {
    --var-anim-bounce-inout-fact: var(--var-circle-size-space);
  }
}

.circle:nth-child(1) {
  --var-anim-delay-fact: -4;
}

.circle:nth-child(2) {
  --var-anim-delay-fact: -3;
}

.circle:nth-child(3) {
  --var-anim-delay-fact: -2;
}

.circle:nth-child(4) {
  --var-anim-delay-fact: -1;
}

.circle:nth-child(5) {
  --var-anim-delay-fact: -0;
}

.circle:last-child {
  --var-anim-bounce-inout-fact: calc(-1*var(--var-circle-size-space));
  --var-anim-bounce-return-amount: calc(-1 * var(--var-circle-count) * var(--var-circle-size-space));
  position: relative;
  animation: var(--var-color-cycle-anim), bounce-inout var(--var-anim-time-unit) var(--var-anim-bounce-func) infinite, bounce-return var(--var-anim-time-unit) ease-in-out infinite, bounce-inout-fact-alternate calc(2*var(--var-anim-time-unit)) steps(1) infinite;
}
<div class="maximized container">
  <div class="items">
    <div class="circle"></div>
    <div class="circle"></div>
    <div class="circle"></div>
    <div class="circle"></div>
    <div class="circle"></div>
  </div>
</div>

可能的解决方法:添加只有 color-cycle 且没有移动动画的圆圈的复制版本,并让它们与原始圆圈重叠,并在动画迭代即将重新开始时将其可见性交换几毫秒.

我有一个理论,将所有内容组合到每个圆圈的一组关键帧中,而不是尝试将项目上的动画与圆圈动画同步,这可能有助于解决相位调整问题。

为了测试它,这里有一个片段可以粗略地重现给定代码的操作。其中的项目 div 没有动画。每个圆圈都保持其原始颜色,并向右、向上、向后、向右、向下、向后动画 - 两端几乎没有弹跳。

未观察到 'flashing/shadowing'。完成的处理量也更少。

关于问题中的代码,我的笔记本电脑显示平均 5% cpu 和 15% 的 gpu 使用率。在此片段中,它们分别约为 1.5% 和 11.5%。

.items {
  --size: 16px;
  --space: 8px;
  --border: solid red;
  --border-width: 0px;
  --shadow: 3px 2px 4px -1px rgba(0, 0, 0, 0.4);
  --shadow-high: 6px 4px 6px -2px rgba(0, 0, 0, 0.2);
  --count: 5;
  --bounce-func: cubic-bezier(0.7, 0.1, 0.6, 1.6);
  --time-unit: calc(1s);
  --cycle-time: calc(2 * var(--count)*var(--time-unit));
  --color-cycle-func: steps(1);
  --color-1: #6db;
  --color-2: #be4;
  --color-3: #ec5;
  --color-4: #d86;
  --color-5: #f8c;
  --slide-time: var(--time-unit);
  --slide-func: linear;
}

.maximized {
  position: fixed;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
}

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

.items {
  font-size: 0;
  white-space: nowrap;
  user-select: none;
  pointer-events: none;
  width: calc((var(--size) + var(--space)) * var(--count));
  height: var(--size);
  position: relative;
}

.circle {
  position: absolute;
  width: var(--size);
  height: var(--size);
  border-radius: 100%;
  box-shadow: var(--shadow);
  display: inline-block;
  margin: 0;
  margin-right: var(--space);
  box-sizing: border-box;
  border: var(--border);
  border-width: min(var(--border-width), calc(var(--size)/2));
  animation: bounceround infinite var(--cycle-time) var(--slide-func);
  animation-delay: var(--delay);
  background-color: var(--color);
}

.circle:nth-child(1) {
  --color: var(--color-1);
  --delay: -8s;
}
.circle:nth-child(2) {
  --color: var(--color-2);
  --delay: -6s;
}
.circle:nth-child(3) {
  --color: var(--color-3);
  --delay: -4s;
}
.circle:nth-child(4) {
  --color: var(--color-4);
  --delay: -2s;
}
.circle:nth-child(5) {
  --color: var(--color-5);
  --delay: 0s;
}
@keyframes bounceround {
  0% {
  }
  36.6666% {
    transform: translateX(calc(var(--count) * 100%)); /* move to the right */
  }
  40% {
    transform: translateX(calc((var(--count) - 0.5) * 100%)) translateY(calc(-100% - var(--size))); /* go up a bit */
  }
  41% {
      transform: translateX(calc((var(--count) - 1) * 100%)) translateY(calc(-100% - (var(--size) / 2))); /* come down a fraction */
  }
  45% {
    transform: translateX(0) translateY(-100%); /* move to the left */
  }
  49% {
    transform: translateX(0) translateY(calc(var(--size) / 4)); /* jump down a bit*/
  }
  50% {
    transform: translateX(0) translateY(0); /* go down to start position */
  }
  86.6666% {
    transform: translateX(calc(var(--count) * 100%)); /* move to the right */
  }
  90% {
    transform: translateX(calc((var(--count) - 0.5) * 100%)) translateY(calc(100% + var(--size))); /* go down a bit */
  }
  91% {
    transform: translateX(calc((var(--count) - 1) * 100%)) translateY(calc(100% + calc(var(--size) / 2))); /* go up a fraction */
  }
  95% {
    transform: translateX(0) translateY(100%); /* move to the left */
  }
  99% {
    transform: translateX(0) translateY(calc(-1 * var(--size) / 4)); /* jump up a bit */
  }
  100% {
    transform: translateX(0) translateY(0); /* back to where we started */
  }
}
<div class="maximized container">
  <div class="items">
    <div class="circle"></div>
    <div class="circle"></div>
    <div class="circle"></div>
    <div class="circle"></div>
    <div class="circle"></div>
  </div>
</div>