You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建带无限动画的多实例React组件并设置独立CSS变量

问题根因

  1. 全局CSS变量冲突:所有组件修改的是:root节点的全局CSS变量,后渲染的组件必然会覆盖之前的变量值,导致所有点共用同一套坐标
  2. 无限动画无法触发结束回调:animation: infinite配置下不会触发animationend事件,无法监听每轮运动结束的时机

修复后的实现代码

1. 调整CSS代码

:root {                 /* 赋值仅为 fallback 备用值 */
  --x: 100px;
  --y1: 10px;
  --y2: 400px;
  --duration: 15s;
}

.spark {
  /* 每个spark组件承载自己的CSS变量,不影响全局 */
}

.spark div {
  height: 20px;
  width: 20px;
  border-radius: 50%;
  position: absolute;
  left: var(--x);
  top: 0; /* 去掉原来的top: var(--y2),避免和transform叠加偏移 */
  background: rgba(0, 0, 0, 1);
  /* 去掉infinite,改成单次动画 */
  animation: myOrbit var(--duration) linear forwards;
}

@keyframes myOrbit {
  0% {
    transform: translateY(var(--y1));
  }
  100% {
    transform: translateY(var(--y2));
  }
}

2. 重写Spark组件代码

import { useRef, useState, useCallback, useEffect } from 'react';

// 生成随机参数的工具函数
const genRandomParams = () => {
  return {
    x: Math.random() * window.innerWidth + 'px',
    y1: Math.random() * window.innerHeight + 'px',
    y2: Math.random() * window.innerHeight + 'px',
    duration: (Math.random() * 10 + 5) + 's' // 随机5-15秒的运动时长,更自然
  }
}

const Spark = () => {
  const sparkRef = useRef(null);
  // 存在state里,参数变化自动触发重渲染
  const [params, setParams] = useState(genRandomParams());

  // 动画结束触发参数更新
  const handleAnimEnd = useCallback(() => {
    setParams(genRandomParams());
  }, []);

  // 每次参数变化,更新当前组件的私有CSS变量
  useEffect(() => {
    const el = sparkRef.current;
    if (!el) return;
    el.style.setProperty("--x", params.x);
    el.style.setProperty("--y1", params.y1);
    el.style.setProperty("--y2", params.y2);
    el.style.setProperty("--duration", params.duration);

    // 触发动画重启动:先移除dom再立即插回,触发动画重计算
    const dot = el.querySelector('div');
    dot.remove();
    el.appendChild(dot);
  }, [params]);

  return (
    <div ref={sparkRef} className="spark" onAnimationEnd={handleAnimEnd}>
      <div />
    </div>
  );
};

export default Spark;

3. 调整App.js批量渲染20个实例

import Spark from "./components/Spark.js";

const App = () => {
  return (
      <div className="sparks" style={{ width: '100vw', height: '100vh', overflow: 'hidden' }}>
        {/* 批量生成20个独立的Spark实例 */}
        {Array.from({ length: 20 }, (_, i) => <Spark key={i} />)}
      </div>
  );
};

export default App;

修复效果说明

  • 每个Spark组件的CSS变量都挂载在自身节点上,互不干扰,不会出现坐标覆盖导致的重叠问题
  • 单次动画结束后会自动触发参数更新,重启动画,实现每轮运动都使用新的随机坐标的需求
  • 所有实例独立运行,参数、运动时长完全隔离

内容的提问来源于stack exchange,提问作者Adityendra Suman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 22:27:03