1. GSAP란?
- 자바스크립트 애니메이션 라이브러리로, 자연스러운 애니메이션을 쉽게 만들 수 있음
- React에서도 gsap + ScrollTrigger 플러그인으로 스크롤 기반 애니메이션 구현 가능
2. React에서 GSAP 사용법 (TypeScript 포함)
- useRef로 DOM 요소 참조
- useEffect에서 gsap.to 또는 ScrollTrigger 초기화
- querySelectorAll<HTMLElement> 로 여러 요소 선택 시 타입 명시 추천
- querySelector<HTMLElement>는 한 개 요소 선택, null 체크 필수
3. 배열 vs 단일 요소 선택
- 여러 요소 다룰 때 querySelectorAll + 반복문 사용
- 한 개 요소만 다룰 때 querySelector + null 체크
- 작은 프로젝트에 한 컴포넌트 내 3개 요소 모두 카운팅 효과 넣으려면 querySelectorAll로 배열 관리 추천
4. 뷰포트 진입/이탈 시 애니메이션 실행
- GSAP ScrollTrigger 사용
- onEnter에서 카운트 애니메이션 시작
- onLeaveBack에서 카운트 초기화(클린업)
- React state(useState)와 useRef 조합해 카운트 상태 관리
5. 숫자 카운팅 애니메이션
- GSAP gsap.to로 객체 내부 숫자 값을 목표 숫자까지 애니메이션
- onUpdate에서 setState 호출해 화면 업데이트
- 숫자 포맷팅은 number.toLocaleString() 으로 천 단위 콤마 추가
6. 코드 구조(컴포넌트 분리)
- 숫자 카운팅 기능을 별도 Counter 컴포넌트로 분리하면 재사용성↑, 유지보수 편리
- 작은 프로젝트거나 한 곳에서만 쓰면 한 컴포넌트에 통합해도 무방
7. GSAP 옵션 설명
- ease: "power1.out"
- 애니메이션 속도를 시작 빠르게, 끝은 천천히 멈추도록 자연스럽게 조절하는 이징 함수
- overwrite: "auto"
- 같은 요소에 겹치는 애니메이션이 있을 때 자동으로 충돌나는 부분만 덮어쓰고 중복 방지
- start : 애니메이션을 시작하기 위한 요소(trigger)와 뷰포트(viewport)의 스크롤 시작 위치
end : 애니메이션을 종료하기 위한 요소(trigger)와 뷰포트(viewport)의 스크롤 끝 위치 - tirgger : ScrollTrigger을 사용할 때 움직이고 싶은 요소를 지정한다.
start: "100px" // 요소의 상단이 뷰포트 스크롤 100px을 지나 시작
start: "top 20%" // 요소의 상단이 스크롤이 80%에 닿으면 시작
start: "top bottom-=100px" // 요소의 상단이 스크롤러 하단에서 100px위에 도달할 때
start: ()=>window.innerHeight // 뷰포트의 높이에 닿을 때 시작
end: "bottom center" // 요소의 하단이 스크롤의 중앙에 보이면 종료
end: "+=300" // 요소의 상단이 스크롤의 300px 더 간 후 종료
toggleClass
: 애니메이션이 start일 때 class추가 end일 때 class제거
toggleClass:'active'
// 요소가 start에 들어올때 active 클래스 추가, end로 넘어가면 active 클래스 제거
참고 :https://velog.io/@kmmkyung/GSAP
8. TypeScript에서의 타입 지정
- querySelectorAll<HTMLElement>로 타입 명시하면 반환 값이 NodeListOf<HTMLElement>가 되어 요소 타입 안전
- querySelector<HTMLElement>는 단일 요소 반환(HTMLElement | null)이므로 null 체크 필요
- 요소가 한 개일 때는 querySelector + null 체크가 권장
9. React ref 안전하게 접근하기
- containerRef.current?.querySelector(...) 같이 optional chaining 쓰면 null 체크 겸용 가능
- 항상 null 체크하고 사용하는 습관이 중요
10. 작은 프로젝트에서 한 컴포넌트에 요소 여러 개 있을 때
- querySelectorAll로 여러 요소 잡아서 반복문 돌면서 각각 ScrollTrigger 생성하고 카운트 상태 배열로 관리
- 뷰포트 진입/이탈마다 개별 카운트 애니메이션 실행, 이탈 시 초기화
npm install gsap
npm install @gsap/react
😁https://codesandbox.io/p/sandbox/4zqpj9
------------------------------------------------------------------------
//숫자길이에 따라 변화하는 width값
const widthInEm = count.toLocaleString().length * 0.6; // 적당히 줄임
<span
className="amount"
style={{
width: `${widthInEm}em`,
display: "inline-block",
whiteSpace: "nowrap",
textAlign: "right",
}}
>
{count.toLocaleString()}
</span>
GSAP 사용시 useEffect 사용 여부 이유/설명
컴포넌트 마운트 후 애니메이션 초기화 | 사용 | DOM이 렌더링된 후에 안전하게 작업 가능 |
애니메이션 라이프사이클 직접 관리 | 사용 | 클린업을 직접 해줘야 하기 때문 |
useGSAP 같은 커스텀 훅 사용할 때 | 안 써도 됨 | 내부에서 이미 useEffect로 관리 중 |
렌더링 중 바로 DOM 조작할 때 | 권장하지 않음 | 렌더링 안정성 문제 가능 |
콜백명 역할
onEnter | 요소가 뷰포트 안으로 들어올 때 처음 실행 |
onLeave | 요소가 뷰포트 밖으로 나갈 때 실행 |
onEnterBack | 스크롤 방향 역방향으로 요소가 뷰포트에 다시 들어올 때 실행 |
onLeaveBack | 역방향으로 요소가 뷰포트 밖으로 나갈 때 실행 |
toggleActions: "play reverse play reverse",
위의 4가지 콜백을 이렇게 줄일 수 있다
toggleActions: "play reverse play reverse"는 GSAP의 ScrollTrigger 옵션 중 하나로, 스크롤 위치에 따라 애니메이션이 어떻게 작동할지를 제어하는 설정입니다.
기본 문법
js
toggleActions: "<onEnter> <onLeave> <onEnterBack> <onLeaveBack>"
네 개의 단계는 다음과 같습니다:
- onEnter: 요소가 스크롤 아래 → 위로 뷰포트에 들어올 때
- onLeave: 요소가 스크롤 아래 → 위로 뷰포트에서 벗어날 때
- onEnterBack: 요소가 스크롤 위 → 아래로 뷰포트에 다시 들어올 때
- onLeaveBack: 요소가 스크롤 위 → 아래로 뷰포트에서 다시 나갈 때
값 설명
- "play": 애니메이션 실행
- "reverse": 애니메이션을 반대로 재생
- "restart": 처음부터 다시 실행
- "none": 아무 동작도 하지 않음
- "pause": 애니메이션 일시정지
"play reverse play reverse" 의미
요소가 아래에서 올라오며 화면에 들어올 때 (onEnter) | 애니메이션 재생 (play) |
요소가 아래에서 더 올라가며 화면에서 벗어날 때 (onLeave) | 애니메이션 역재생 (reverse) |
요소가 위에서 내려오며 다시 화면에 들어올 때 (onEnterBack) | 애니메이션 재생 (play) |
요소가 다시 아래로 내려가며 화면에서 벗어날 때 (onLeaveBack) | 애니메이션 역재생 (reverse) |
즉:
✔️ 뷰포트 진입 → 애니메이션 실행
❌ 뷰포트 이탈 → 애니메이션 되감기
👆 아래서 올라와도, 👇 위에서 내려와도 똑같이 동작함
예시
ts
ScrollTrigger.create({
trigger: ".box",
toggleActions: "play reverse play reverse",
animation: gsap.to(".box", { x: 100, duration: 1 }),
});
- .box가 화면에 들어오면 → 오른쪽으로 이동 (x: 100)
- 나가면 → 왼쪽으로 다시 돌아옴
* useGSAP은 GSAP과 React 훅을 결합해주고 scope 옵션으로 특정 ref 안에서만 작동하게 해줘서 편리하다.
import React, { useRef } from "react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { useGSAP } from "@gsap/react";
gsap.registerPlugin(ScrollTrigger);
const SingleBoxWithHook: React.FC = () => {
const proceed = useRef<HTMLDivElement | null>(null);
useGSAP(
() => {
const box = proceed.current;
if (!box) return;
gsap.to(box, {
x: 150,
scrollTrigger: {
trigger: box,
start: "top top",
end: "bottom bottom",
scrub: true,
},
});
},
{ scope: proceed }
);
return (
<div
ref={proceed}
className="proceeds"
style={{
width: "100px",
height: "100px",
backgroundColor: "lightcoral",
margin: "50px",
}}
>
Single Box
</div>
);
};
export default SingleBoxWithHook;
*useEffect와 useGSAP 훅 사용 차이
상황 useEffect 사용 여부 이유/설명
컴포넌트 마운트 후 애니메이션 초기화 | 사용 | DOM이 렌더링된 후에 안전하게 작업 가능 |
애니메이션 라이프사이클 직접 관리 | 사용 | 클린업을 직접 해줘야 하기 때문 |
useGSAP 같은 커스텀 훅 사용할 때 | 안 써도 됨 | 내부에서 이미 useEffect로 관리 중 |
렌더링 중 바로 DOM 조작할 때 | 권장하지 않음 | 렌더링 안정성 문제 가능 |
tsx
// useEffect 직접 사용 useEffect(() => {
const box = ref.current;
if (!box) return;
const anim = gsap.to(box, { x: 100 });
return () => anim.kill(); // 클린업
}, []);
tsx
// useGSAP 훅 사용
useGSAP(() => { gsap.to(ref.current, { x: 100 }); }, { scope: ref });
+++ gsap + animate.css 활용해보기
👆toggleClass 를 이용할수도 있지만 html에 직접써도 상관없을거 같아서 써보았다.
아주 잘된다. 간단한 translateY같은것은 gsap에 있는것을 이용하고 다양한 효과를 원할경우 animate.css를 활용하면 좋을것 같다.
Animate.css | A cross-browser library of CSS animations.
Animate.css is a library of ready-to-use, cross-browser animations for you to use in your projects. Great for emphasis, home pages, sliders, and attention-guiding hints.
animate.style
설치 : npm install animate.css --save
app.tsx에
import "animate.css";
이게 끝이다.. 너무 간단😁
+++ gsap를 활용한 애니메이션 (큰화면에서 스크롤해서 보기)
설치
npm install gsap
npm install @gsap/react
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import "animate.css";
gsap.registerPlugin(ScrollTrigger);
animate.css와 합한 코드이다. 간단하다..!
다들 시도해 보시길👍
'React' 카테고리의 다른 글
useRef (0) | 2025.06.04 |
---|---|
react에서 modal (0) | 2025.06.04 |
리액트 비디오 (0) | 2025.05.20 |
react에서 swiper 사용하기 (0) | 2025.05.20 |
여러 페이지에서 scrollTo 사용하기 (0) | 2025.05.20 |