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

Next.js项目中GSAP ScrollTrigger触发功能失效问题排查

Next.js 中 GSAP ScrollTrigger 滚动触发失效问题排查与修复

问题场景

在Next.js项目中使用GSAP结合ScrollTrigger实现滚动触发动画,动画本身可正常运行,但滚动触发功能完全失效。涉及组件包括:

  • AnimationEffect:封装GSAP与ScrollTrigger的动画逻辑
  • Hero:设定为滚动触发的目标区块
  • HandleRequest:页面入口,负责调用各组件

问题代码展示

AnimationEffect 组件

import React, { useEffect } from 'react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/dist/ScrollTrigger';

export const AnimationEffect = () => {
  gsap.registerPlugin(ScrollTrigger);

  useEffect(() => {
    gsap.fromTo(
      '.hero-left-wrap',
      {
        autoAlpha: 0,
        x: -30,
      },
      {
        autoAlpha: 1,
        x: 0,
        duration: 0.6,
        delay: 0.5,
        stagger: 0.2,
        ease: 'none',
        scrollTrigger: {
          trigger: 'section',
          toggleActions: 'play none none none',
        },
      }
    );
    gsap.fromTo(
      '.hero-right-wrap',
      {
        autoAlpha: 0,
        x: 30,
      },
      {
        autoAlpha: 1,
        x: 0,
        duration: 0.6,
        delay: 0.5,
        stagger: 0.2,
        ease: 'none',
        scrollTrigger: {
          trigger: 'section',
          toggleActions: 'play none none none',
        },
      }
    );
  }, []);

  useEffect(() => {
    if (ScrollTrigger) {
      ScrollTrigger.saveStyles('.hero-left-wrap, .hero-right-wrap');
      ScrollTrigger.refresh();
    }
  }, []);

  return true;
};

export default AnimationEffect;

HandleRequest 页面入口

import cms from '@/cms';
import templates from '@/components/templates';
import Layout from '@/components/layout';
import AnimationEffect from '@/components/gsap';

export default function HandleRequest({ $global, data }) {
    const template = data?.content?.template || '**';
    const Template = templates[template];

    return (
        <Layout $global={$global}>
            <AnimationEffect />
            {!!Template && <Template data={data} />}
            {!Template && <pre>Template not found: {template}</pre>}
        </Layout>
    );
}

export async function getServerSideProps(ctx) {
    const response = await cms.handle(ctx, {
        $global: await cms.getGlobalData(),
    });

    if (response?.props?.notFound) {
        ctx.res.statusCode = 404;
    }

    return response;
}

Hero 组件(触发区块)

import VideoComp from "@/components/elements/VideoComp";
import Link from "next/link";
import ClutchReview from "../../elements/ClutchReview";

const Hero = ({ data }) => {
  let { title, description, buttons, media_type, video_url, enabledReview , image } =
    data;
  enabledReview = true;

  return (
    <section className="hero-section pt-10 pb-24">
      <div className="container">
        <div className="grid grid-cols-2 gap-20">
          <div className="hero-left-wrap">
            <h1 className="font-semibold text-5xl text-secondary leading-[60px]">
              {title}
            </h1>
            <p className="font-medium text-2xl text-dim mt-6 leading-9">
              {description}
            </p>

            {buttons && (
              <div className="mt-12 flex flex-wrap gap-6">
                {buttons.map((butData, id) => (
                  <Link
                    href={butData.button.url}
                    className={
                      butData.type === "primary"
                        ? "primary-btn"
                        : "secondary-btn"
                    }
                    key={id}
                  >
                    {butData.button.title}
                  </Link>
                ))}
              </div>
            )}

            {enabledReview && <ClutchReview />}
          </div>
          <div className="hero-right-wrap relative">
            <VideoComp data={{media_type, video_url,image}} />
          </div>
        </div>
      </div>
    </section>
  );
};

export default Hero;

核心问题与修复方案

1. 触发目标选择器不精准

当前ScrollTrigger使用trigger: 'section'会匹配页面中所有<section>元素,导致触发逻辑混乱。需指定Hero组件的唯一类名作为触发目标:

scrollTrigger: {
  trigger: '.hero-section', // 改用Hero组件的专属类名,确保唯一匹配
  toggleActions: 'play none none none',
}

2. 组件渲染顺序与DOM就绪问题

AnimationEffect在HandleRequest中先于Hero组件渲染,此时目标DOM元素(.hero-left-wrap、.hero-right-wrap)尚未挂载,GSAP无法找到元素初始化ScrollTrigger。

最优修复:
将动画逻辑移至Hero组件内部,确保目标元素存在时再初始化GSAP:

import React, { useEffect } from 'react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/dist/ScrollTrigger';
import VideoComp from "@/components/elements/VideoComp";
import Link from "next/link";
import ClutchReview from "../../elements/ClutchReview";

const Hero = ({ data }) => {
  let { title, description, buttons, media_type, video_url, enabledReview , image } = data;
  enabledReview = true;

  useEffect(() => {
    gsap.registerPlugin(ScrollTrigger);

    gsap.fromTo(
      '.hero-left-wrap',
      { autoAlpha: 0, x: -30 },
      {
        autoAlpha: 1,
        x: 0,
        duration: 0.6,
        delay: 0.5,
        stagger: 0.2,
        ease: 'none',
        scrollTrigger: {
          trigger: '.hero-section',
          toggleActions: 'play none none none',
        },
      }
    );

    gsap.fromTo(
      '.hero-right-wrap',
      { autoAlpha: 0, x: 30 },
      {
        autoAlpha: 1,
        x: 0,
        duration: 0.6,
        delay: 0.5,
        stagger: 0.2,
        ease: 'none',
        scrollTrigger: {
          trigger: '.hero-section',
          toggleActions: 'play none none none',
        },
      }
    );

    // 组件卸载时清理ScrollTrigger,避免内存泄漏
    return () => {
      ScrollTrigger.getAll().forEach(trigger => trigger.kill());
    };
  }, []);

  return (
    <section className="hero-section pt-10 pb-24">
      {/* 原有内容保持不变 */}
    </section>
  );
};

export default Hero;

同时删除HandleRequest中的<AnimationEffect />引用,避免重复初始化。

3. Next.js SSR 兼容性问题

GSAP和ScrollTrigger依赖浏览器环境,SSR阶段执行会导致异常。若仍需使用独立的AnimationEffect组件,可通过Next.js的dynamic导入并禁用SSR:

// 在HandleRequest中修改导入方式
import dynamic from 'next/dynamic';
const AnimationEffect = dynamic(() => import('@/components/gsap'), { ssr: false });

4. ScrollTrigger 刷新时机问题

原代码中ScrollTrigger.refresh()执行时机过早,需将其与动画初始化放在同一个useEffect中,确保动画配置完成后再刷新:

useEffect(() => {
  gsap.registerPlugin(ScrollTrigger);

  // 初始化所有动画...

  ScrollTrigger.refresh();

  return () => {
    ScrollTrigger.getAll().forEach(trigger => trigger.kill());
  };
}, []);

验证步骤

  1. 确认Hero组件的.hero-section类名唯一,无其他冲突
  2. 检查DOM元素是否在动画初始化前已挂载完成
  3. 滚动到Hero区块位置,验证动画是否正常触发

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 02:02:37