所有分类
  • 所有分类
  • Html5资源
  • React资源
  • Vue资源
  • Php资源
  • ‌小程序资源
  • Python资源

react gsap动画库使用详解之ui动画

简介

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

Flip

Flip 是 gsap 提供的一个插件,用于实现翻转动画。

3d 卡片翻转动画

import { useRef, useState } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { Flip } from "gsap/Flip";

import "../styles/flip.css";

gsap.registerPlugin(useGSAP, Flip);

export default function GaspFlipDemo() {
  const [isFlipped, setIsFlipped] = useState(false);
  const containerRef = useRef(null);
  const cardRef = useRef(null);

  useGSAP(() => {
    const card = cardRef.current;
    if (!card) return;

    const flipState = Flip.getState(card);

    if (isFlipped) {
      card.style.transform = "rotateY(180deg)";
    } else {
      card.style.transform = "rotateY(0deg)";
    }

    Flip.from(flipState, {
      duration: 0.8,
      ease: "power2.inOut",
      onComplete: () => {
        // 动画完成后的回调
      },
    });
  }, [isFlipped]);

  return (
    <div className="w-full h-[500px] flex items-center justify-center">
      <div
        ref={containerRef}
        className="perspective-1000"
        onClick={() => setIsFlipped(!isFlipped)}
      >
        <div
          ref={cardRef}
          className="w-[300px] h-[400px] relative cursor-pointer transition-transform duration-300"
          style={{ transformStyle: "preserve-3d" }}
        >
          {/* 卡片正面 */}
          <div className="absolute w-full h-full bg-blue-500 rounded-lg flex items-center justify-center text-white text-2xl backface-hidden">
            Front
          </div>
          {/* 卡片背面 */}
          <div
            className="absolute w-full h-full bg-red-500 rounded-lg flex items-center justify-center text-white text-2xl backface-hidden"
            style={{ transform: "rotateY(180deg)" }}
          >
            Back
          </div>
        </div>
      </div>
    </div>
  );
}
.perspective-1000 {
  perspective: 1000px;
}

.backface-hidden {
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

拖拽排序

import { useRef, useState } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { Flip } from "gsap/Flip";

gsap.registerPlugin(useGSAP, Flip);

export default function GaspFlipDemo() {
  const [items, setItems] = useState([
    { id: 1, color: "bg-blue-500" },
    { id: 2, color: "bg-green-500" },
    { id: 3, color: "bg-red-500" },
    { id: 4, color: "bg-yellow-500" },
  ]);
  const [draggedItem, setDraggedItem] = useState(null);
  const containerRef = useRef(null);

  const handleDragStart = (e, item) => {
    setDraggedItem(item);
    const target = e.target;

    // 创建拖拽时的动画效果
    gsap.to(target, {
      scale: 1.1,
      rotation: 5,
      duration: 0.2,
      boxShadow: "0 10px 20px rgba(0,0,0,0.2)",
      zIndex: 100,
    });
  };

  const handleDragOver = (e, targetItem) => {
    e.preventDefault();
    if (!draggedItem || draggedItem.id === targetItem.id) return;

    // 为放置目标添加悬停效果
    gsap.to(e.target, {
      scale: 1.05,
      duration: 0.2,
      boxShadow: "0 5px 15px rgba(0,0,0,0.1)",
    });
  };

  const handleDragLeave = (e) => {
    // 移除悬停效果
    gsap.to(e.target, {
      scale: 1,
      duration: 0.2,
      boxShadow: "none",
    });
  };

  const handleDrop = (e, targetItem) => {
    e.preventDefault();
    if (!draggedItem) return;

    const draggedIndex = items.findIndex((item) => item.id === draggedItem.id);
    const targetIndex = items.findIndex((item) => item.id === targetItem.id);

    // 获取所有元素的当前状态
    const state = Flip.getState(".item", {
      props: "backgroundColor,boxShadow",
      simple: true,
    });

    // 更新状态
    const newItems = [...items];
    [newItems[draggedIndex], newItems[targetIndex]] = [
      newItems[targetIndex],
      newItems[draggedIndex],
    ];
    setItems(newItems);

    // 执行动画
    Flip.from(state, {
      duration: 0.6,
      ease: "power2.inOut",
      onComplete: () => {
        // 重置所有元素的样式
        gsap.to(".item", {
          scale: 1,
          rotation: 0,
          boxShadow: "none",
          duration: 0.3,
          clearProps: "all",
        });
        setDraggedItem(null);
      },
    });
  };

  return (
    <div className="w-full h-[500px] flex items-center justify-center">
      <div
        ref={containerRef}
        className="grid grid-cols-2 gap-4 p-4 bg-gray-100 rounded-lg"
      >
        {items.map((item) => (
          <div
            key={item.id}
            className={`item w-32 h-32 ${item.color} rounded-lg cursor-move flex items-center justify-center text-white text-xl transition-all`}
            draggable
            onDragStart={(e) => handleDragStart(e, item)}
            onDragOver={(e) => handleDragOver(e, item)}
            onDragLeave={handleDragLeave}
            onDrop={(e) => handleDrop(e, item)}
          >
            {item.id}
          </div>
        ))}
      </div>
    </div>
  );
}

Draggable

拖拽效果

import { useRef, useState, useEffect } from "react";
import { gsap } from "gsap";
import { Draggable } from "gsap/Draggable";

gsap.registerPlugin(Draggable);

export default function GaspFlipDemo() {
  const boxRef = useRef(null);
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    if (!boxRef.current) return;

    // 创建可拖拽实例
    const draggable = Draggable.create(boxRef.current, {
      type: "x,y",
      onDrag: function () {
        // 更新位置状态
        setPosition({
          x: this.x,
          y: this.y,
        });
      },
      onDragStart: function () {
        // 拖拽开始时的动画
        gsap.to(this.target, {
          scale: 1.1,
          duration: 0.2,
        });
      },
      onDragEnd: function () {
        // 拖拽结束时的动画
        gsap.to(this.target, {
          scale: 1,
          duration: 0.2,
        });
      },
    });

    // 清理函数
    return () => {
      draggable[0].kill();
    };
  }, []);

  return (
    <div
      ref={boxRef}
      className="absolute w-[200px] h-[200px] bg-blue-500 rounded-lg cursor-move flex items-center justify-center text-white"
      style={{
        left: "50%",
        top: "50%",
        transform: "translate(-50%, -50%)",
      }}
    >
      Drag Me
      <div className="text-sm mt-1">
        X: {Math.round(position.x)} Y: {Math.round(position.y)}
      </div>
    </div>
  );
}

滑动验证

X 轴方向滑动特效

import { useRef, useState, useEffect } from "react";
import { gsap } from "gsap";
import { Draggable } from "gsap/Draggable";

gsap.registerPlugin(Draggable);

export default function GaspFlipDemo() {
  const sliderRef = useRef(null);
  const trackRef = useRef(null);
  const [isVerified, setIsVerified] = useState(false);
  const [sliderPosition, setSliderPosition] = useState(0);
  const sliderWidth = 40; // 滑块宽度
  const trackWidth = 280; // 轨道实际可滑动宽度

  useEffect(() => {
    if (!sliderRef.current || !trackRef.current) return;

    const draggable = Draggable.create(sliderRef.current, {
      type: "x",
      bounds: {
        minX: 0,
        maxX: trackWidth - sliderWidth, // 限制最大滑动距离,考虑滑块宽度
      },
      onDrag: function () {
        const progress = (this.x / (trackWidth - sliderWidth)) * 100;
        setSliderPosition(this.x);

        // 更新滑块背景,使用伪元素实现圆角
        gsap.to(trackRef.current, {
          background: `linear-gradient(to right, #4CAF50 ${progress}%, #e0e0e0 ${progress}%)`,
          duration: 0.1,
        });
      },
      onDragEnd: function () {
        // 验证是否滑动到终点
        if (this.x >= trackWidth - sliderWidth - 5) {
          setIsVerified(true);
          gsap.to(trackRef.current, {
            background: "#4CAF50",
            duration: 0.3,
          });
        } else {
          // 未完成验证,滑块回弹
          gsap.to(sliderRef.current, {
            x: 0,
            duration: 0.3,
            ease: "power2.out",
          });
          gsap.to(trackRef.current, {
            background: "#e0e0e0",
            duration: 0.3,
          });
          setSliderPosition(0);
        }
      },
    });

    return () => {
      draggable[0].kill();
    };
  }, []);

  return (
    <div className="w-full h-[100vh] flex items-center justify-center">
      <div className="w-[320px] p-4 bg-white rounded-lg shadow-lg">
        <h3 className="text-lg font-medium mb-4">滑动验证</h3>
        {/* 滑块轨道 */}
        <div
          ref={trackRef}
          className="relative h-[40px] bg-[#e0e0e0] rounded-full overflow-hidden"
          style={{ width: `${trackWidth}px` }}
        >
          {/* 滑块 */}
          <div
            ref={sliderRef}
            className="absolute w-[40px] h-[40px] bg-white rounded-full shadow-md cursor-move flex items-center justify-center"
            style={{
              left: 0,
              top: 0,
              transform: `translateX(${sliderPosition}px)`,
            }}
          >
            <svg
              className="w-5 h-5 text-gray-500"
              fill="none"
              stroke="currentColor"
              viewBox="0 0 24 24"
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth={2}
                d="M9 5l7 7-7 7"
              />
            </svg>
          </div>
        </div>
        {/* 验证状态 */}
        <div className="mt-4 text-center">
          {isVerified ? (
            <span className="text-green-500">验证成功!</span>
          ) : (
            <span className="text-gray-500">向右滑动完成验证</span>
          )}
        </div>
      </div>
    </div>
  );
}

InertiaPlugin

惯性插件,用于实现惯性滑动效果

import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { InertiaPlugin } from "gsap/InertiaPlugin";
import { Draggable } from "gsap/Draggable";

gsap.registerPlugin(InertiaPlugin, Draggable);

export default function GsapUiDemo() {
  const cardRef = useRef(null);
  const containerRef = useRef(null);

  useGSAP(() => {
    if (!cardRef.current || !containerRef.current) return;

    // 创建可拖动实例
    Draggable.create(cardRef.current, {
      type: "x,y",
      inertia: true,
      bounds: containerRef.current,
      onDragStart: function () {
        gsap.set(this.target, { zIndex: 100 });
      },
      onDragEnd: function () {
        gsap.set(this.target, { zIndex: 1 });
      },
    });
  }, []);

  return (
    <div
      ref={containerRef}
      className="w-full h-[100vh] overflow-hidden relative bg-gray-100"
    >
      <div
        ref={cardRef}
        className="absolute w-48 h-48 bg-white rounded-lg shadow-lg cursor-move"
        style={{
          top: "50%",
          left: "50%",
          transform: "translate(-50%, -50%)",
        }}
      >
        <div className="p-4">
          <h3 className="text-xl font-bold mb-2">可拖动卡片</h3>
          <p className="text-gray-600">尝试拖动我,感受惯性效果</p>
        </div>
      </div>
    </div>
  );
}

Observer

观察者插件,用于监听元素状态变化,如进入视口、离开视口等

import { useEffect } from "react";
import { gsap } from "gsap";
import { Observer } from "gsap/Observer";

export default function GsapObserverDemo() {
  const boxRef = useRef(null);

  useEffect(() => {
    if (!boxRef.current) return;

    const observer = Observer.create({
      target: boxRef.current,
      type: "visibility",
      callback: (self) => {
        if (self.isIntersecting) {
          // 元素进入视口
          gsap.to(self.target, {
            scale: 1.2,
            duration: 0.3,
          });
        } else {
          // 元素离开视口
          gsap.to(self.target, {
            scale: 1,
            duration: 0.3,
          });
        }
      },
    });

    return () => {
      observer.kill();
    };
  }, []);

  return (
    <div className="w-full h-[100vh] flex items-center justify-center">
      <div
        ref={boxRef}
        className="w-[100px] h-[100px] bg-red-500 rounded-full"
      ></div>
    </div>
  );
}
原文链接:https://code.ifrontend.net/archives/340,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?