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

Framer Motion文本动画与GSAP自定义光标动画性能干扰问题排查及修复方案咨询

Framer Motion文本动画与GSAP自定义光标动画性能干扰问题排查及修复方案咨询

嘿,我之前也碰到过类似的动画冲突问题,结合你给出的代码和技术栈,咱们来一步步分析问题出在哪,以及怎么修复它~

问题原因分析

这两个动画互相干扰主要有以下几个关键点:

  • 高频事件的动画实例堆积:mousemove事件每秒会触发几十次,你当前的代码每次触发都会用GSAP创建一个新的to动画实例,这些实例会抢占浏览器的动画帧资源,导致Framer Motion的文本动画得不到足够的帧时间,出现卡顿。
  • 定时器精度被干扰:你的AnimatedText组件用了setTimeout来调度下一次动画,而setTimeout的执行依赖浏览器的事件循环。当mousemove事件频繁触发时,事件队列被占满,setTimeout的回调会被延迟执行,直接打乱了文本动画的时序。
  • 动画引擎的帧调度冲突:Framer Motion和GSAP都基于requestAnimationFrame实现动画,但如果GSAP的光标动画频繁抢占帧,会导致Framer Motion的文本动画帧被挤压,出现视觉上的停顿。

具体修复方案

下面是几个可以单独或组合使用的修复方法,亲测有效:

方案一:优化GSAP光标动画的触发逻辑,避免重复创建动画

不要每次mousemove都创建新动画,而是先记录鼠标位置,再用GSAP的ticker统一驱动动画,同时开启overwrite: auto避免动画实例堆积:

import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';

export const CustomCursor = () => {
  const cursorRef = useRef(null);
  const mousePosRef = useRef({ x: window.innerWidth / 2, y: window.innerHeight / 2 });

  useEffect(() => {
    // 初始化光标位置
    gsap.set(cursorRef.current, { 
      x: mousePosRef.current.x, 
      y: mousePosRef.current.y 
    });

    // 只更新鼠标位置,不直接创建动画
    const handleMouseMove = (e) => {
      mousePosRef.current.x = e.clientX;
      mousePosRef.current.y = e.clientY;
    };
    window.addEventListener('mousemove', handleMouseMove);

    // 用GSAP的ticker统一驱动动画,复用实例
    const ticker = gsap.ticker.add(() => {
      gsap.to(cursorRef.current, {
        x: mousePosRef.current.x,
        y: mousePosRef.current.y,
        duration: 0.3,
        ease: "power2.out",
        overwrite: "auto" // 自动覆盖未完成的动画,避免堆积
      });
    });

    // 清理监听和ticker
    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
      gsap.ticker.remove(ticker);
    };
  }, []);

  return (
    <div ref={cursorRef} style={{
      position: 'fixed',
      width: '20px',
      height: '20px',
      backgroundColor: '#000',
      borderRadius: '50%',
      pointerEvents: 'none',
      zIndex: 9999,
      willChange: 'transform' // 告诉浏览器提前优化渲染层
    }} />
  );
};

方案二:替换setTimeout为更可靠的时序控制

setTimeout的精度不足,建议改用更严谨的定时器管理,让文本动画的调度和动画引擎同步:

"use client";
import React, { useCallback, useEffect, useState, useRef } from "react";
import { AnimatePresence, motion } from "motion/react";

export const AnimatedText = ({ items, duration = 3000 }) => {
  const [currentItem, setCurrentItem] = useState(items[0]);
  const [isAnimating, setIsAnimating] = useState(false);
  const timerRef = useRef(null);

  const startAnimation = useCallback(() => {
    const nextItem = items[items.indexOf(currentItem) + 1] || items[0];
    setCurrentItem(nextItem);
    setIsAnimating(true);
  }, [currentItem, items]);

  useEffect(() => {
    const scheduleNext = () => {
      timerRef.current = setTimeout(() => {
        startAnimation();
      }, duration);
    };

    if (!isAnimating) scheduleNext();

    // 组件卸载或依赖变化时清理定时器
    return () => {
      if (timerRef.current) clearTimeout(timerRef.current);
    };
  }, [isAnimating, duration, startAnimation]);

  return (
    <AnimatePresence onExitComplete={() => setIsAnimating(false)}>
      <motion.div
        initial={{ opacity: 0, y: 10 }}
        animate={{ opacity: 1, y: 0 }}
        exit={{ opacity: 0, y: -40, x: 40, filter: "blur(8px)", scale: 2 }}
        transition={{ type: "spring", stiffness: 100, damping: 10 }}
        key={currentItem}
      >
        {currentItem.split(" ").map((word, wordIndex) => (
          <motion.span
            key={word + wordIndex}
            initial={{ opacity: 0, y: 10, filter: "blur(8px)" }}
            animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
            transition={{ delay: wordIndex * 0.3, duration: 0.3 }}
          >
            {word.split("").map((letter, letterIndex) => (
              <motion.span
                key={word + letterIndex}
                initial={{ opacity: 0, y: 10, filter: "blur(8px)" }}
                animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
                transition={{
                  delay: wordIndex * 0.3 + letterIndex * 0.05,
                  duration: 0.2,
                }}
              >
                {letter}
              </motion.span>
            ))}
          </motion.span>
        ))}
      </motion.div>
    </AnimatePresence>
  );
};

方案三:启用GSAP的自动休眠功能

GSAP默认有autoSleep功能,闲置一段时间后会暂停动画引擎,减少资源占用,你可以在初始化时开启:

useEffect(() => {
  // 闲置1秒后自动休眠动画引擎
  gsap.config({ autoSleep: 1000 });
  // ... 其他光标动画代码
}, []);

总结

优先尝试方案一优化光标动画的触发逻辑,这是解决冲突最关键的一步;如果还有轻微卡顿,再结合方案二和三调整文本动画的时序和GSAP的配置,基本就能让两个动画独立运行互不干扰了。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.09 03:03:18