在通过数组的后续循环中,我的 React 文本动画中的时间变得更糟

Timing in my React text animation gets worse on subsequent loops through an array

我在 codesandbox and on my staging site.

中有带有 CSS 动画的 React 代码

您会注意到,随着时间的推移,动画时间会漂移。在一定数量的循环后它过早呈现文本并且与动画不同步。

我试过改变时间使阵列切换发生得更快或更慢。

如有任何想法,我们将不胜感激。

import "./styles.css";
import styled, { keyframes } from "styled-components";
import React, { useEffect, useState } from "react";

const animation = keyframes`
  0% { opacity: 0; transform: translateY(-100px) skewX(10deg) skewY(10deg) rotateZ(30deg); filter: blur(10px); }
  25% { opacity: 1; transform: translateY(0px) skewX(0deg) skewY(0deg) rotateZ(0deg); filter: blur(0px); }
  75% { opacity: 1; transform: translateY(0px) skewX(0deg) skewY(0deg) rotateZ(0deg); filter: blur(1px); }
  100% { opacity: 0; transform: translateY(-100px) skewX(10deg) skewY(10deg) rotateZ(30deg); filter: blur(10px); }
`;

const StaticText = styled.div`
  position: absolute;
  top: 100px;
  h1 {
    color: #bcbcbc;
  }
  span {
    color: red;
  }
  h1,
  span {
    font-size: 5rem;
    @media (max-width: 720px) {
      font-size: 3rem;
    }
  }
  width: 50%;
  text-align: center;
  left: 50%;
  margin-left: -25%;
`;

const Animate = styled.span`
  display: inline-block;

  span {
    opacity: 0;
    display: inline-block;
    animation-name: ${animation};
    animation-duration: 3s;
    animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1);
    animation-fill-mode: forwards;
    animation-iteration-count: infinite;
    font-weight: bold;
  }
  span:nth-child(1) {
    animation-delay: 0.1s;
  }
  span:nth-child(2) {
    animation-delay: 0.2s;
  }
  span:nth-child(3) {
    animation-delay: 0.3s;
  }
  span:nth-child(4) {
    animation-delay: 0.4s;
  }
  span:nth-child(5) {
    animation-delay: 0.5s;
  }
`;

export default function App() {
  const array = ["wood", "cork", "leather", "vinyl", "carpet"];

  const [text, setText] = useState(array[0].split(""));

  const [countUp, setCountUp] = useState(0);

  useEffect(() => {
    const id = setTimeout(() => {
      if (countUp === array.length -1) {
        setCountUp(0);
      } else {
        setCountUp((prev) => prev + 1);
      }
    }, 3000);

    return () => {
      clearTimeout(id);
    };
  }, [countUp]);

  useEffect(() => {
    setText(array[countUp].split(""));
  }, [countUp]);

  return (
    <div className="App">
      <StaticText>
        <h1>More than just</h1>
        <Animate>
          {text.map((item, index) => (
            <span key={index}>{item}</span>
          ))}
        </Animate>
      </StaticText>
    </div>
  );
}

这里有多个潜在问题。一方面,动画 运行s 长达 3.5 秒(由于延迟),但文本每 3 秒更改一次,因此文本更改将在最后一个字符完成动画之前触发。

即使文本和动画都设置为 3 秒,问题是 CSS 动画和 setTimeout/setInterval 时序并不完美。这些应该被认为是粗略的估计。 setTimeout 可能需要 3 秒或 3.1 秒才能触发,即使它按时触发,React 也必须在设置另一个之前完成工作。漂移可以而且将会发生,因此只要文本发生变化,动画就应该 运行 以 event-driven 的方式,而不是我们假设将与 React 和超时保持同步的无限循环。

您可以尝试解决这些问题的调整包括:

  1. 删除animation-iteration-count: infinite;属性。这使我们有责任触发动画以响应 re-renders,而不是在单独的 likely-out-of-sync 循环中。

  2. setTimeout 超时更改为 3500,或至少与最长动画持续时间一样大的值,以确保动画不会中途中断。

  3. 为您的字母 <span> 提供随机密钥以强制重新呈现,如 中所述。准确地说,可能是 <span key={Math.random()}>{item}</span>.

    您可以使用 Math.random() 进行按键冲突,因此使用递增状态计数器或集成 Date.now() 按键是更可靠的方法。