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

React中GSAP文字动画初始加载卡顿,热重载正常求助

React中GSAP文字动画初始加载卡顿问题排查与修复

问题现象

在React组件中使用GSAP结合SplitType实现文字拆分动画时,所有设备上初始页面加载或刷新时动画出现卡顿/停滞,但开发环境热重载时动画完全正常。

相关代码

import { useEffect, useRef } from 'react';
import SplitType from 'split-type';
import gsap from "gsap";
import ScrollTrigger from "gsap/ScrollTrigger";

gsap.registerPlugin(ScrollTrigger);

const HeroSection = () => {
  const headingRef = useRef(null);
  const bodyRef = useRef(null);

  useEffect(() => {
    const text = new SplitType(headingRef.current, { types: 'chars' });
    const body = new SplitType(bodyRef.current, { types: 'chars' });

    const tl = gsap.timeline({
      defaults: {
        duration: 0.5,
        ease: "back.out",
      }
    });

    tl.from(text.chars, {
      opacity: 0,
      y: 20,
      rotateX: -90,
      stagger: 0.05
    })
      .from(body.chars, {
        opacity: 0,
        x: 50,
        stagger: 0.01,
        ease: "power2.out"
      }, "-=0.3"); 

    return () => {
      text.revert();
      body.revert();
      tl.kill();
    };
  }, []);

  return (
    <div className="overflow-hidden">
      <h1
        ref={headingRef}
        className="font-light text-5xl md:text-6xl lg:text-7xl text-black leading-[1.1] mb-8 opacity-0">
        {/* 标题文本 */}
      </h1>
      <p ref={bodyRef} className="text-sm md:text-base text-black/70 max-w-md mb-12 leading-relaxed opacity-0">
        {/* 正文文本 */}
      </p>
    </div>
  );
};

效果对比

  • 初始加载卡顿效果:
    初始加载卡顿效果
  • 热重载正常效果:
    热重载正常效果

已尝试方案

  • 添加动画延迟
  • 使用gsap.context()管理动画上下文
  • 为元素设置willChange: 'transform'
  • 调整动画时序与缓动函数
    以上方案均未解决问题。

问题原因分析

  1. 初始资源未就绪:页面首次加载时,浏览器需同时处理字体加载、样式计算、DOM布局等任务,SplitType拆分DOM节点和GSAP动画启动时机过早,此时DOM尚未完成稳定布局,动画执行时触发频繁的回流/重绘,导致卡顿。而热重载时,资源已缓存、DOM状态稳定,动画不受阻塞。
  2. CSS初始状态冲突:通过className设置opacity:0,可能导致浏览器在初始渲染时提前触发重排,与GSAP动画的初始状态设置产生冲突,加剧卡顿。
  3. 主线程阻塞:首次加载时的资源解析、脚本执行等长任务占用主线程,导致动画帧丢失,出现停滞。

修复方案

方案1:等待资源完全就绪后启动动画

通过document.fonts.ready确保字体加载完成,避免因字体未加载导致的布局偏移和动画卡顿:

useEffect(() => {
  const initAnimation = async () => {
    // 等待所有字体加载完成
    await document.fonts.ready;
    
    const text = new SplitType(headingRef.current, { types: 'chars' });
    const body = new SplitType(bodyRef.current, { types: 'chars' });

    // 用GSAP设置初始状态,替代CSS中的opacity:0
    gsap.set([text.chars, body.chars], { autoAlpha: 0 });

    const tl = gsap.timeline({
      defaults: { duration: 0.5, ease: "back.out" }
    });

    tl.from(text.chars, {
      y: 20,
      rotateX: -90,
      stagger: 0.05,
      autoAlpha: 1
    })
    .from(body.chars, {
      x: 50,
      stagger: 0.01,
      ease: "power2.out",
      autoAlpha: 1
    }, "-=0.3");

    return () => {
      text.revert();
      body.revert();
      tl.kill();
    };
  };

  initAnimation();
}, []);

同时移除JSX元素className中的opacity:0,避免初始状态冲突。

方案2:使用useLayoutEffect替代useEffect

useLayoutEffect在DOM更新后同步执行,能确保动画启动时DOM已经完成布局计算,避免因异步布局导致的卡顿:

// 替换useEffect为useLayoutEffect
useLayoutEffect(() => {
  // 动画逻辑与之前一致
}, []);

方案3:启用硬件加速优化

为动画元素添加硬件加速提示,减少重绘开销:

tl.from(text.chars, {
  opacity: 0,
  y: 20,
  rotateX: -90,
  stagger: 0.05,
  willChange: 'transform, opacity' // 提示浏览器提前优化
})

方案4:优化主线程性能

通过Lighthouse分析页面加载性能,排查并优化初始加载时的长任务(如大体积脚本、未优化的图片),释放主线程资源给动画执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 15:13:13