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

React TypeScript发光加载Spinner性能优化方案咨询

性能优化:100元素发光Spinner的React TypeScript组件优化方案

问题根源

当前实现通过100个嵌套DOM元素分别执行独立CSS动画,每个动画都会触发浏览器重绘/重排,占用大量主线程资源,导致客户端其他交互和处理流程卡顿。

优化方案(保留原视觉效果)

方案1:CSS层面优化,降低动画开销

通过硬件加速减少浏览器渲染压力,同时简化DOM结构:

  1. 给动画元素添加GPU加速属性,让浏览器提前分配资源
  2. 将2D变换替换为3D变换,强制触发硬件加速
  3. 简化DOM嵌套层级,减少节点数量

修改后的CSS:

.spinner i {
  display: block;
  position: absolute;
  opacity: 1;
  margin-top: -3px;
  margin-left: -4px;
  will-change: transform; /* 告知浏览器提前优化 */
  transform: translateZ(0); /* 触发GPU加速 */
}

.spinner i b {
  display: block;
  width: 6px;
  height: 6px;
  border-radius: 3px;
  background: white;
  box-shadow: 0px 0px 14px white;
}

.spinAnimation {
  animation-name: spin;
  animation-duration: 3s;
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
}

@keyframes spin {
  0% {
    transform: scale3d(1, 1, 1); /* 3D缩放 */
  }
  15% {
    transform: translate3d(-3px, -3px, 0) scale3d(3, 3, 1); /* 3D位移+缩放 */
  }
  50% {
    transform: scale3d(1, 1, 1);
  }
}

/* 原nth-child规则保留,仅修改transform为3D版本 */
.spinner i:nth-child(1) {
  transform: rotate3d(0,0,1,3.6deg) translate3d(100px, 0, 0);
}
/* ... 其余nth-child规则同理替换为rotate3d和translate3d */

React组件简化DOM:
将<i><b className={animateClass}></b></i>替换为单个<i className={animateClass}></i>,并调整CSS中.spinner i的样式直接包含圆点和发光效果,减少DOM节点数量从200个降至100个。


方案2:CSS变量+动态生成元素,简化代码并优化控制

用CSS变量替代冗余的nth-child规则,通过React动态生成元素,减少代码冗余同时保持动画逻辑清晰:

优化后的React组件:

import React, { FC } from 'react';
import './GlowingSpinner.css';

const defaultProps = { animate: true };
type SpinnerProps = Partial<typeof defaultProps>;

const GlowingSpinner: FC<SpinnerProps> = ({ animate = true }) => {
  // 动态生成100个点的索引
  const dotIndices = Array.from({ length: 100 }, (_, idx) => idx + 1);

  return (
    <>
      {dotIndices.map((index) => (
        <i
          key={index}
          className={animate ? 'spinAnimation' : ''}
          style={{
            '--rotate': `${3.6 * index}deg`,
            '--delay': `${0.03061 * index}s`
          } as React.CSSProperties}
        ></i>
      ))}
    </>
  );
};

export default GlowingSpinner;

对应的CSS:

.spinner {
  position: relative;
  width: 200px;
  height: 200px;
}

.spinner i {
  display: block;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -3px;
  margin-left: -3px;
  width: 6px;
  height: 6px;
  border-radius: 3px;
  background: white;
  box-shadow: 0px 0px 14px white;
  /* 用CSS变量控制旋转角度 */
  transform: rotate(var(--rotate)) translate3d(100px, 0, 0);
  will-change: transform;
}

.spinAnimation {
  animation: spin 3s infinite ease-in-out;
  /* 用CSS变量控制动画延迟 */
  animation-delay: var(--delay);
  transform: translateZ(0);
}

@keyframes spin {
  0% { transform: scale3d(1,1,1); }
  15% { transform: translate3d(-3px, -3px, 0) scale3d(3,3,1); }
  50% { transform: scale3d(1,1,1); }
}

方案3:Canvas替代DOM元素,彻底解决性能问题

用单个Canvas元素绘制所有动画点,完全消除100个DOM元素的渲染开销,动画通过requestAnimationFrame驱动,性能提升最显著:

Canvas版本React组件:

import React, { FC, useEffect, useRef } from 'react';

const defaultProps = { animate: true };
type SpinnerProps = Partial<typeof defaultProps>;

const GlowingSpinner: FC<SpinnerProps> = ({ animate = true }) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const animationIdRef = useRef<number | null>(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;
    const radius = 100;
    const dotSize = 6;
    const glowSize = 14;
    const totalDots = 100;
    const animationDuration = 3000; // 动画周期3秒

    // 绘制单个圆点(含发光效果)
    const drawDot = (x: number, y: number, scale: number) => {
      ctx.shadowBlur = glowSize;
      ctx.shadowColor = 'white';
      ctx.fillStyle = 'white';
      ctx.beginPath();
      ctx.arc(x, y, dotSize * scale, 0, Math.PI * 2);
      ctx.fill();
      ctx.shadowBlur = 0;
    };

    // 动画帧逻辑
    const animate = () => {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      const currentTime = Date.now() % animationDuration;

      for (let i = 0; i < totalDots; i++) {
        // 计算当前点的角度(弧度)
        const angle = (i * 3.6 * Math.PI) / 180;
        // 计算当前点的位置
        const x = centerX + radius * Math.cos(angle);
        const y = centerY + radius * Math.sin(angle);
        // 计算当前点的动画延迟(毫秒)
        const delay = i * 0.03061 * 1000;
        // 计算当前点的本地动画时间
        const localTime = (currentTime - delay + animationDuration) % animationDuration;

        let scale = 1;
        // 模拟原CSS动画的缩放逻辑
        if (localTime < animationDuration * 0.15) {
          scale = 1 + (2 * localTime) / (animationDuration * 0.15);
        } else if (localTime < animationDuration * 0.5) {
          scale = 3 - (2 * (localTime - animationDuration * 0.15)) / (animationDuration * 0.35);
        }

        drawDot(x, y, scale);
      }

      animationIdRef.current = requestAnimationFrame(animate);
    };

    if (animate) {
      animationIdRef.current = requestAnimationFrame(animate);
    } else {
      // 停止动画,绘制静态状态
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      for (let i = 0; i < totalDots; i++) {
        const angle = (i * 3.6 * Math.PI) / 180;
        const x = centerX + radius * Math.cos(angle);
        const y = centerY + radius * Math.sin(angle);
        drawDot(x, y, 1);
      }
    }

    // 清理动画
    return () => {
      if (animationIdRef.current) {
        cancelAnimationFrame(animationIdRef.current);
      }
    };
  }, [animate]);

  return <canvas ref={canvasRef} width={200} height={200} />;
};

export default GlowingSpinner;

使用方式:

<div id="Spinner">
  <GlowingSpinner animate={isLoading} />
</div>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 17:10:25