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' - 调整动画时序与缓动函数
以上方案均未解决问题。
问题原因分析
- 初始资源未就绪:页面首次加载时,浏览器需同时处理字体加载、样式计算、DOM布局等任务,SplitType拆分DOM节点和GSAP动画启动时机过早,此时DOM尚未完成稳定布局,动画执行时触发频繁的回流/重绘,导致卡顿。而热重载时,资源已缓存、DOM状态稳定,动画不受阻塞。
- CSS初始状态冲突:通过className设置
opacity:0,可能导致浏览器在初始渲染时提前触发重排,与GSAP动画的初始状态设置产生冲突,加剧卡顿。 - 主线程阻塞:首次加载时的资源解析、脚本执行等长任务占用主线程,导致动画帧丢失,出现停滞。
修复方案
方案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
相关产品推荐
相关产品推荐

