개발/React

[React] GSAP(ScrollTrigger)을 활용한 가로/세로 효과

hr0513 2024. 11. 13. 16:03
728x90
반응형

안녕하세요, hr0513입니다! 🥰

 

제가 프론트엔드 포트폴리오를 만드느라 오늘 연속으로 블로그 글을 올리는 중인데요,

그중 GSAP를 활용하여  가로/세로 스크롤 애니메이션 구현 방법을 공유하려고 합니다.


gsap 설치

gsap를 설치해 주세요.

npm install gsap

애니메이션 적용

const horizontal = document.querySelector("#horizontal");
const sections = gsap.utils.toArray("#horizontal > section");

gsap.to(sections, {
  xPercent: -100 * (sections.length - 1),
  ease: "none",
  scrollTrigger: {
    trigger: horizontal,
    start: "top top",
    end: () => "+=" + (horizontal.offsetWidth - innerWidth),
    pin: true,
    scrub: 1,
    snap: {
      snapTo: 1 / (sections.length - 1),
      inertia: false,
      duration: { min: 0.1, max: 0.1 },
    },
    invalidateOnRefresh: true,
    anticipatePin: 1,
  },
});

 

- gsap.to(sections, {...}): 각 섹션에 대한 애니메이션을 정의합니다.

이 애니메이션은 스크롤할 때 sections가 수평으로 이동하는 효과를 냅니다.

 

- xPercent: -100 * (sections.length - 1): 각 섹션이 얼마나 이동할지 결정하는 부분입니다.

 

- scrollTrigger 옵션

start: "top top": 스크롤 트리거가 #hor의 상단과 화면 상단이 일치할 때 시작됩니다.

end: 트리거가 끝나는 위치를 계산하는데요, 화면 크기(innerWidth)와 hor.offsetWidth를 이용해 계산합니다.

pin: true: 스크롤 중 해당 요소를 고정시킵니다.

scrub: 1: 스크롤의 속도와 애니메이션을 일치시킵니다.

snap: 스크롤이 특정 위치에 맞춰 스냅 되도록 설정합니다.

invalidateOnRefresh: 화면이 리프레시되면 트리거를 다시 계산합니다.

anticipatePin: 요소가 핀 고정되기 전에 미리 계산하여 매끄럽게 스크롤 애니메이션이 동작하도록 합니다.


전체 코드

"use client";

import { useEffect } from 'react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

const scrollExample = () => {
  useEffect(() => {
    gsap.registerPlugin(ScrollTrigger);
    
    const horizontal = document.querySelector("#horizontal");
    const sections = gsap.utils.toArray("#horizontal > section");

    gsap.to(sections, {
      xPercent: -100 * (sections.length - 1),
      ease: "none",
      scrollTrigger: {
        trigger: horizontal,
        start: "top top",
        end: () => "+=" + (horizontal.offsetWidth - innerWidth),
        pin: true,
        scrub: 1,
        snap: {
          snapTo: 1 / (sections.length - 1),
          inertia: false,
          duration: { min: 0.1, max: 0.1 },
        },
        invalidateOnRefresh: true,
        anticipatePin: 1,
      },
    });
  }, []);

  const sectionStyle = {
    flexShrink: 0,
    width: '100vw',
    height: '100vh',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    background: '#111',
    color: '#fff',
    fontSize: '40px',
    fontWeight: '500',
  };

  const horizontalStyle = {
    display: 'flex',
    overflowX: 'auto',
    scrollSnapType: 'x mandatory',
    width: '100%',
  };

  return (
    <main>
      <section style={sectionStyle}>
        <span>01</span>
      </section>

      <section style={sectionStyle}>
        <span>02</span>
      </section>

      <main id="horizontal" style={horizontalStyle}>
        <section style={sectionStyle}>
          <span>03</span>
        </section>

        <section style={sectionStyle}>
          <span>04</span>
        </section>

        <section style={sectionStyle}>
          <span>05</span>
        </section>
      </main>

      <section style={sectionStyle}>
        <span>06</span>
      </section>
      
      <section style={sectionStyle}>
        <span>07</span>
      </section>
    </main>
  );
};

export default scrollExample;

실행 화면

728x90
반응형