react gsap动画库使用详解之scroll滑动动画

简介

gsap 高性能的 JavaScript 动画库,在现代网页设计和开发中运用。

安装

复制代码
npm install gsap

React 框架中使用

可以考滤使用 react-gsap-enhancer 库,或者 @gasp/react

类组件使用 react-gsap-enhancer 高阶组件,函数组件使用 @gasp/react 自定义 Hook。

复制代码
npm install react-gsap-enhancer
#or
yarn add react-gsap-enhancer

ScrollTrigger

ScrollTrigger 用于在滚动时触发动画。如滚动到指定位置时触发动画、滚动到指定位置时结束动画、滚动到指定位置时重复动画等。

基本使用

复制代码
import React from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";

gsap.registerPlugin(useGSAP, ScrollTrigger);

const TextAnimation = () => {
  useGSAP(() => {
    gsap.fromTo(
      ".gasp-text",
      {
        y: 100, // 初始位置
      },
      {
        scrollTrigger: {
          trigger: ".gasp-text", // 触发动画的元素
          start: "top bottom-=100", // 动画开始的位置
          toggleActions: "play none none reverse", // 动画触发时的行为
          markers: true, // 显示标记
        },
        duration: 1, // 动画持续时间
        y: 0, // 动画结束时的位置
      }
    );
  });

  return (
    <div>
      <div className="gasp-text">hello world</div>
    </div>
  );
};

常用属性

  • trigger: 触发动画的元素
  • start: 动画开始的位置,可以是相对于触发元素的位置,也可以是相对于视口的位置
  • end: 动画结束的位置,可以是相对于触发元素的位置,也可以是相对于视口的位置
  • toggleActions: 动画触发时的行为,可以是 "play"(播放)、"pause"(暂停)、"resume"(恢复)、"reset"(重置)、"restart"(重新开始)。 按顺序为 onEnter、onLeave、onEnterBack 和 onLeaveBack,默认值 play none none none。进入 离开 返回进入 返回离开
  • markers: 是否显示标记,用于调试

页面视差滚动

复制代码
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";

gsap.registerPlugin(useGSAP, ScrollTrigger);

export default function GaspScrollDemo() {
  const sectionsRef = useRef([]);
  const titlesRef = useRef([]);
  const descsRef = useRef([]);

  // 滚动触发的动画
  useGSAP(() => {
    sectionsRef.current.forEach((section, index) => {
      // 创建时间线
      const tl = gsap.timeline({
        scrollTrigger: {
          trigger: section,
          start: "top top",
          end: "bottom top",
          pin: true,
          pinSpacing: true,
          scrub: false,
        },
      });

      // 随机方向数组
      const directions = [
        { x: -100, y: 0 },
        { x: 100, y: 0 },
        { x: 0, y: -100 },
        { x: 0, y: 100 },
        { x: -100, y: -100 },
        { x: 100, y: 100 },
        { x: -100, y: 100 },
      ];

      // 进入动画
      tl.fromTo(
        [titlesRef.current[index], descsRef.current[index]],
        {
          ...directions[index],
          opacity: 0,
          scale: 0.5,
          rotation: Math.random() * 30 - 15,
        },
        {
          x: 0,
          y: 0,
          opacity: 1,
          scale: 1,
          rotation: 0,
          duration: 1,
          ease: "power2.out",
        }
      );

      // 停留动画
      tl.to(
        [titlesRef.current[index], descsRef.current[index]],
        {
          scale: 1.05,
          duration: 0.5,
          ease: "power1.inOut",
        },
        "+=0.2"
      ).to([titlesRef.current[index], descsRef.current[index]], {
        scale: 1,
        duration: 0.5,
        ease: "power1.inOut",
      });

      // 离开动画
      tl.to(
        [titlesRef.current[index], descsRef.current[index]],
        {
          y: -100,
          opacity: 0,
          scale: 0.8,
          rotation: -Math.random() * 30 + 15,
          duration: 1,
          ease: "power2.in",
        },
        "+=0.5"
      );
    });
  }, []);

  const sections = [
    {
      color: "bg-red-500",
      title: "Red Section",
      desc: "A vibrant red section with dynamic animations",
    },
    {
      color: "bg-orange-500",
      title: "Orange Section",
      desc: "Warm orange tones with smooth transitions",
    },
    {
      color: "bg-yellow-500",
      title: "Yellow Section",
      desc: "Bright yellow energy with playful movements",
    },
    {
      color: "bg-green-500",
      title: "Green Section",
      desc: "Fresh green space with natural flow",
    },
    {
      color: "bg-blue-500",
      title: "Blue Section",
      desc: "Calm blue atmosphere with gentle animations",
    },
    {
      color: "bg-indigo-500",
      title: "Indigo Section",
      desc: "Deep indigo depth with smooth transitions",
    },
    {
      color: "bg-purple-500",
      title: "Purple Section",
      desc: "Rich purple elegance with dynamic effects",
    },
  ];

  return (
    <div className="relative">
      {sections.map((section, index) => (
        <div
          key={index}
          ref={(el) => (sectionsRef.current[index] = el)}
          className={`${section.color} h-screen w-full relative overflow-hidden flex items-center justify-center`}
        >
          <div className="text-center z-10 px-4">
            <h2
              ref={(el) => (titlesRef.current[index] = el)}
              className="text-6xl font-bold text-white mb-6"
            >
              {section.title}
            </h2>
            <p
              ref={(el) => (descsRef.current[index] = el)}
              className="text-xl text-white/80 max-w-2xl mx-auto"
            >
              {section.desc}
            </p>
          </div>
        </div>
      ))}
    </div>
  );
}

ScrollSmoother

ScrollSmoother 为基于 ScrollTrigger 的页面添加了垂直平滑滚动效果。

基本使用

复制代码
import { useEffect, useRef, useState } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { ScrollSmoother } from "gsap/ScrollSmoother";
import { ScrollToPlugin } from "gsap/ScrollToPlugin";

gsap.registerPlugin(useGSAP, ScrollTrigger, ScrollSmoother, ScrollToPlugin);

export default function GaspScrollDemo() {
  const [activeSection, setActiveSection] = useState("box1");
  const sectionsRef = useRef([]);
  const titlesRef = useRef([]);
  const descsRef = useRef([]);

  // 创建动画时间线的函数
  const createAnimation = (index) => {
    const tl = gsap.timeline({
      scrollTrigger: {
        trigger: sectionsRef.current[index],
        start: "top center",
        end: "bottom center",
        toggleActions: "play none none reverse",
      },
    });

    tl.fromTo(
      titlesRef.current[index],
      {
        y: 100,
        opacity: 0,
      },
      {
        y: 0,
        opacity: 1,
        duration: 1,
        ease: "power2.out",
      }
    );

    tl.fromTo(
      descsRef.current[index],
      {
        y: 50,
        opacity: 0,
      },
      {
        y: 0,
        opacity: 1,
        duration: 1,
        ease: "power2.out",
      },
      "-=0.5"
    );

    return tl;
  };

  useGSAP(() => {
    // 创建平滑滚动
    const smoother = ScrollSmoother.create({
      wrapper: "#smooth-wrapper",
      content: "#smooth-content",
      smooth: 1.5,
      effects: true,
    });

    // 为每个部分创建 ScrollTrigger
    sectionsRef.current.forEach((section, index) => {
      ScrollTrigger.create({
        trigger: section,
        start: "top center",
        end: "bottom center",
        onEnter: () => {
          setActiveSection(`box${index + 1}`);
          // 如果是第一屏,手动触发动画
          if (index === 0) {
            createAnimation(0);
          }
        },
        onEnterBack: () => {
          setActiveSection(`box${index + 1}`);
          // 如果是第一屏,手动触发动画
          if (index === 0) {
            createAnimation(0);
          }
        },
      });

      // 创建动画
      createAnimation(index);
    });

    // 初始加载时触发第一屏动画
    createAnimation(0);
  }, []);

  const goToBox = (id) => {
    gsap.to(window, {
      duration: 1,
      scrollTo: {
        y: `#${id}`,
        offsetY: 0,
        autoKill: false,
      },
      ease: "power2.inOut",
    });
  };

  const buttons = [
    { id: "box1", color: "bg-red-500", activeColor: "bg-red-900" },
    { id: "box2", color: "bg-orange-500", activeColor: "bg-orange-900" },
    { id: "box3", color: "bg-yellow-500", activeColor: "bg-yellow-900" },
    { id: "box4", color: "bg-green-500", activeColor: "bg-green-900" },
    { id: "box5", color: "bg-blue-500", activeColor: "bg-blue-900" },
    { id: "box6", color: "bg-indigo-500", activeColor: "bg-indigo-900" },
  ];

  const sections = [
    {
      id: "box1",
      title: "Section 1",
      desc: "Welcome to Section 1",
    },
    {
      id: "box2",
      title: "Section 2",
      desc: "Welcome to Section 2",
    },
    {
      id: "box3",
      title: "Section 3",
      desc: "Welcome to Section 3",
    },
    {
      id: "box4",
      title: "Section 4",
      desc: "Welcome to Section 4",
    },
    {
      id: "box5",
      title: "Section 5",
      desc: "Welcome to Section 5",
    },
    {
      id: "box6",
      title: "Section 6",
      desc: "Welcome to Section 6",
    },
  ];

  return (
    <div id="smooth-wrapper" className="h-screen overflow-hidden">
      <div className="fixed top-[30%] right-[10px] flex-col flex items-center gap-4 z-50">
        {buttons.map((button) => (
          <button
            key={button.id}
            className={`w-10 h-10 transition-all duration-300 border-2 border-black ${
              activeSection === button.id ? button.activeColor : button.color
            }`}
            onClick={() => goToBox(button.id)}
          />
        ))}
      </div>
      <div id="smooth-content" className="relative">
        {sections.map((section, index) => (
          <div
            key={section.id}
            ref={(el) => (sectionsRef.current[index] = el)}
            id={section.id}
            className={`${buttons[index].color} h-screen w-full relative flex items-center justify-center`}
          >
            <div className="text-center z-10 px-4 max-w-3xl">
              <h2
                ref={(el) => (titlesRef.current[index] = el)}
                className="text-6xl font-bold text-white mb-6"
              >
                {section.title}
              </h2>
              <p
                ref={(el) => (descsRef.current[index] = el)}
                className="text-xl text-white/80"
              >
                {section.desc}
              </p>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

ScrollToPlugin

滚动到指定位置

基本使用

复制代码
import { useEffect, useRef, useState } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { ScrollToPlugin } from "gsap/ScrollToPlugin";

gsap.registerPlugin(useGSAP, ScrollTrigger, ScrollToPlugin);

export default function GaspScrollDemo() {
  const [activeSection, setActiveSection] = useState("box1");
  const sectionsRef = useRef([]);

  useGSAP(() => {
    // 为每个部分创建 ScrollTrigger
    sectionsRef.current.forEach((section, index) => {
      ScrollTrigger.create({
        trigger: section,
        start: "top center",
        end: "bottom center",
        onEnter: () => setActiveSection(`box${index + 1}`),
        onEnterBack: () => setActiveSection(`box${index + 1}`),
      });
    });
  }, []);

  const goToBox = (id) => {
    gsap.to(window, {
      duration: 1,
      scrollTo: {
        y: `#${id}`,
        offsetY: 0,
        autoKill: false,
      },
      ease: "power2.inOut",
    });
  };

  const buttons = [
    { id: "box1", color: "bg-red-500", activeColor: "bg-red-900" },
    { id: "box2", color: "bg-orange-500", activeColor: "bg-orange-900" },
    { id: "box3", color: "bg-yellow-500", activeColor: "bg-yellow-900" },
    { id: "box4", color: "bg-green-500", activeColor: "bg-green-900" },
    { id: "box5", color: "bg-blue-500", activeColor: "bg-blue-900" },
    { id: "box6", color: "bg-indigo-500", activeColor: "bg-indigo-900" },
  ];

  return (
    <div className="box">
      <div className="fixed top-[30%] right-[10px] flex-col flex items-center gap-4">
        {buttons.map((button) => (
          <button
            key={button.id}
            className={`w-10 h-10 transition-all duration-300 border-2 border-black ${
              activeSection === button.id ? button.activeColor : button.color
            }`}
            onClick={() => goToBox(button.id)}
          />
        ))}
      </div>
      <div
        ref={(el) => (sectionsRef.current[0] = el)}
        id="box1"
        className="bg-red-500 h-screen w-full"
      />
      <div
        ref={(el) => (sectionsRef.current[1] = el)}
        id="box2"
        className="bg-orange-500 h-screen w-full"
      />
      <div
        ref={(el) => (sectionsRef.current[2] = el)}
        id="box3"
        className="bg-yellow-500 h-screen w-full"
      />
      <div
        ref={(el) => (sectionsRef.current[3] = el)}
        id="box4"
        className="bg-green-500 h-screen w-full"
      />
      <div
        ref={(el) => (sectionsRef.current[4] = el)}
        id="box5"
        className="bg-blue-500 h-screen w-full"
      />
      <div
        ref={(el) => (sectionsRef.current[5] = el)}
        id="box6"
        className="bg-indigo-500 h-screen w-full"
      />
    </div>
  );
}

更多使用示例

相关推荐
超人不会飛3 分钟前
就着HTTP聊聊SSE的前世今生
前端·javascript·http
蓝胖子的多啦A梦6 分钟前
Vue+element 日期时间组件选择器精确到分钟,禁止选秒的配置
前端·javascript·vue.js·elementui·时间选选择器·样式修改
夏天想9 分钟前
vue2+elementui使用compressorjs压缩上传的图片
前端·javascript·elementui
The_cute_cat10 分钟前
JavaScript的初步学习
开发语言·javascript·学习
海天胜景13 分钟前
vue3 el-table 列增加 自定义排序逻辑
javascript·vue.js·elementui
今晚打老虎z17 分钟前
dotnet-env: .NET 开发者的环境变量加载工具
前端·chrome·.net
用户38022585982423 分钟前
vue3源码解析:diff算法之patchChildren函数分析
前端·vue.js
烛阴28 分钟前
XPath 进阶:掌握高级选择器与路径表达式
前端·javascript
小鱼小鱼干31 分钟前
【JS/Vue3】关于Vue引用透传
前端
JavaDog程序狗33 分钟前
【前端】HTML+JS 实现超燃小球分裂全过程
前端