프로젝트 초반부터 꼭 구현하고 싶었던 애니메이션 중 하나는-
아티스트 검색 창에서 사진을 스크롤 했을 때, 해당 이미지가 스크롤 양에 상관 없이 화면 상단 또는 하단에 snap 되고, 이전 / 다음 사진이 레이어 카드처럼 쌓여나가는 방식이다. (ZARA 홈페이지 메인화면을 참고했다)
이 부분을 구현하기 위해 position: sticky 를 사용하여 스크롤 이벤트를 감지하여 생성된 div가 뷰포트 상단에 맞춰 쌓여나가도록 하려 했다.
또한 1초 동안 이벤트 핸들러를 제거하여 지나치게 빠른 스크롤을 방지하려 했다.
그러나 스크롤을 조금만 하더라도 이전 / 다음 div가 스냅되듯이 나타나는 방식을 구현하는 데 어려움을 겪고 있었다.
이 때 우연히 GSAP 이라는 라이브러리를 알게 되었다.
공식 홈페이지의 튜토리얼 영상을 보고 그대로 따라하며 익혀보려 한다.
우선 플러그인을 설치해준다.
yarn add gsap
튜토리얼 영상에서는 vanila JS를 사용하기에, 오랜만에 새로운 프로젝트를 생성해 따라하며 실습해보았다.
기본적인 HTML, CSS, JS 구조를 생성하고, CSS파일과 JS파일, GSAP을 연결한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>gsap test project</title>
<link rel="stylesheet" href="styles.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.0/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.0/ScrollTrigger.min.js"></script>
</head>
<body>
<div class="box a">a</div>
<div class="box b">b</div>
<div class="box c">c</div>
<script src="script.js"></script>
</body>
</html>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f0f0f0;
}
.box {
width: 100px;
height: 100px;
}
.a {
background-color: aqua;
margin-bottom: 300px;
}
.b {
background-color: antiquewhite;
margin-bottom: 300px;
}
.c {
background-color: tomato;
margin-bottom: 300px;
}
사용법은 매우 직관적이고 간단하다.
gsap.to("해당 애니메이션을 입힐 클래스 또는 ID", {
scrollTrigger: 화면에 보여졌을 때 실행할 기준 클래스 또는 ID,
x: 가로축 이동 끝 좌표 (단위는 px, %, 함수 모두 가능),
rotation: 각도,
duration: 지속시간,
});
객체 형태로 작성하기 때문에 ',' 를 잊지 말고 써주자.
더 나아가 scrollTrigger의 값을 객체로 입력하면 더욱 다양하고 유연한 동작을 끌어낼 수 있다.
gsap.to("해당 애니메이션을 입힐 클래스 또는 ID", {
scrollTrigger: {
trigger: "뷰포트 등장 기준 클래스 또는 ID",
toggleActions: "정방향진입 정방향퇴장 역방향진입 역방향퇴장",
},
x: 가로축 이동 끝 좌표 (단위는 px, %, 함수 모두 가능),
rotation: 각도,
duration: 지속시간,
});
toggleActions 에 들어가는 4가지 요소에 주목해보자.
초기값은 아래와 같다.
toggleActions: "play none none none"
재생 옵션은 다음과 같다.
- play : 일반 재생
- pause : 뷰포트에서 보이지 않을 경우 그 자리에서 정지
- resume : 정지 시점에서부터 다시 재생
- reverse : 거꾸로 재생 (보통 다시 scroll up할 때 적용)
- restart : 처음부터 다시 재생
- reset : 초기 상태로
- complete : 완료 상태로
- none: 아무 동작도 하지 않음
이것을 바탕으로 c 블럭이 화면에 나타난 경우 c 블럭에 다음 조건의 애니메이션을 재생시켜보자.
1. c 블럭에 GSAP 지정
2. c 블럭이 화면에 나타난 경우 애니메이션 실행
3. 정방향으로 스크롤 했을 때 재생
4. 뷰포트에서 사라졌을 때 애니메이션 잠시 멈춤
5. 역방향으로 스크롤해서 c 블럭이 다시 뷰포트에 나타난 경우 거꾸로 재생
gsap.to(".c", {
scrollTrigger: {
trigger: ".c",
toggleActions: "play pause reverse none",
},
x: 1000,
rotation: 360,
duration: 3,
});
참고로 뷰포트의 진입, 퇴장 기준점은 뷰포트 최상단과 최하단이 default로 지정되어 있다.
만일 이 좌표를 바꾸고 싶다면?
scrollTrigger의 객체값에 start과 end 속성을 추가해주자.
두 기준점이 정확히 어디인지 시각적으로 확인하기 위해 markers 속성을 추가하면 편하다.
gsap.to(".c", {
scrollTrigger: {
trigger: ".c",
toggleActions: "play pause reverse none",
markers: true,
start: "요소의기준점 뷰포트기준점",
end: "요소의기준점 뷰포트기준점",
},
x: 1000,
rotation: 360,
duration: 3,
});
start과 end의 값으로는 top, bottom, center 뿐 아니라 해당 요소의 최상단을 기준으로 px 및 % 단위도 사용할 수 있음을 참고하자.
또한 end의 경우, '+=' 를 사용하여 start값의 상대값을 입력할 수 있다.
예를 들어, 요소의 중심을 시작점으로 삼고 100px아래에서 애니메이션을 끝내고 싶다면 아래와 같이 작성한다.
gsap.to(".c", {
scrollTrigger: {
trigger: ".c",
toggleActions: "play pause reverse none",
markers: true,
start: "center 80%",
end: "+=100",
},
x: 1000,
rotation: 360,
duration: 3,
});
그러나 반응형으로 구현한다면 고정값을 매기는 것이 부적합할 수 있다.
이 때 end값에 함수를 직접 넣을 수도 있다.
(개인적으로 GSAP의 가장 멋진 부분이라고 생각한다)
참고: end값을 하나만 입력하면 그 값은 트리거 요소의 end point로 간주되고, 뷰포트의 end point는 start에서 지정한 값과 일치한다.
gsap.to(".c", {
scrollTrigger: {
trigger: ".c",
toggleActions: "play pause reverse none",
markers: true,
start: "center 80%",
end: () => "+=" + document.querySelector(".c").offsetWidth,
},
x: 1000,
rotation: 360,
duration: 3,
});
위와 같이 작성한다면 end점은 c블럭의 오프셋너비 / 2 만큼 아래에 지정된다.
start point가 center이기 때문에 2로 나눈 것이다.
물론 적용할 대상, trigger 대상, endTrigger 대상 모두 다른 요소로 지정할 수도 있다.
그렇다면 스크롤을 위 아래로 짧게 이동할때마다 애니메이션이 왔다갔다(?) 하게 해보자.
scrub 속성을 통해 구현할 수 있다.
gsap.to(".c", {
scrollTrigger: {
trigger: ".c",
start: "top center",
end: "top 100px",
scrub: boolean 또는 초단위
},
x: 1000,
rotation: 360,
duration: 3,
});
이렇게 하면 가로로 이동하는 도중 역방향 스크롤이 감지되면 즉시 애니메이션이 reverse 된다.
scrub의 값으로 들어간 초는 latency를 나타낸다.
start point와 end point까지 거리에 비례하여 애니메이션 타임라인이 진행된다.
다시 말해, start와 end의 거리가 500px 이고 250px만큼 스크롤 했다면, 애니메이션은 절반만큼 진행되어 있다.
이 타임라인을 변수로 선언하고 재사용한다면 다음과 같다.
let customTimeline = gsap.timeline({
scrollTrigger: {
trigger: ".c",
start: "top center",
end: "top 100px",
scrub: 1,
}
});
customTimeline.to(".c", {
x: 1000,
rotation: 360,
duration: 3,
ease: "none",
});
기본적인 사용 방법을 익혔으니, 다음 글에서 구현하고 싶었던 기능을 구현해보자.
'React.ts' 카테고리의 다른 글
Firebase 정보 새로고침 시 초기화 되는 오류 (1) | 2024.09.19 |
---|---|
GSAP 활용하여 ScrollEvent 쉽게 구현하기 - 2 (0) | 2024.08.12 |
React TypeScript - OpenAI DALL.E API로 이미지 생성하기 (0) | 2024.07.17 |
React TypeScript - Custom Hook으로 모달창 관리하기 (0) | 2024.07.07 |
React TypeScript - Intersection Observer 활용 (0) | 2024.07.04 |