# 19.gsap

In 
ui

# 목차

  • 1. gsap
  • 2. 설치
    • 2.1. cdn
    • 2.2. npm
    • 2.3. css transform과 gsap 속성 비교
    • 2.4. 기타속성
  • 3. Tween
    • 3.1. 예제
  • 4. easing
    • 4.1. 예제
  • 5. gsap.timeline()
    • 5.1. 예제
  • 6. scrollTrigger
    • 6.1. 주요속성
    • 6.2. 주요메서드
    • 6.3. 예제
  • 7. 가로방향 스크롤
    • 예제

# 1. gsap

gsap공식사이트
https://gsap.com/
gsap코드펜
https://codepen.io/GreenSock
gsap치트시트
https://greensock.com/cheatsheet/
gsap의 모든 기능이 있는 cdn 링크
https://cdnjs.com/libraries/gsap

웹 브라우저에서 애니메이션을 구현하기 위한 자바스크립트 라이브러리 기존 CSS나 순수 자바스크립트보다 탁월한 퍼포먼스를 발휘할 수 있도록 최적화 된 애니메이션 전용 라이브러리이며, HTML5와 Flash 두 개의 플랫폼에서 사용할 수 있다. GreenSock Engaging the Internet GASP은 애니메이션을 쉽게 적용할 수 있는 라이브러리로 J-Query 보다 20배 이상 빠른 성능을 갖고 있다고 소개하고 있다.

  • gsap.to() 메소드를 이용해 자연스러운 움직임을 만들 수 있다

  • 다양한 종류의 애니메이션을 제공하며 필요에 따라 플러그인 형태로 추가하여 사용할수 있다. (일부유료)

  • 기본 기능만을 사용할때는 gsap.js 혹은 gsap.min.js 을 사용한다.

  • 문법은 camelCase 를 사용한다. ex) background-color (x), backgroundColor (O)

  • left 및 top과 같은 위치 속성을 애니메이션화할 때 이동하려는 요소에 CSS position 속성을 적용해야한다.


# 2. 설치

# 2.1. cdn

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>

# 2.2. npm

npm install gsap

# 2.3. css transform과 gsap 속성 비교

gsap css
x: 100 transform: translateX(100px)
y: 100 transform: translateY(100px)
rotation: 360 transform: rotate(360deg)
rotationX: 360 transform: rotateX(360deg)
rotationY: 360 transform: rotateY(360deg)
skewX: 45 transform: skewX(45px)
skewY: 45 transform: skewY(100px)
scale: 2 transform: scale(2,2)
scaleX: 2 transform: scaleX(2)
scaleY: 2 transform: scaleY(2)
xPercent: -50 transform: translateX(-50%)
yPercent: -50 transform: translateY(-50%)

# 2.4. 기타속성

속성 설명
delay 애니메이션 시작 전 지연 시간 지정
repeat 반복횟수 지정, -1(무한반복)
repeatDelay 반복 전 지연 시간 지정
yoyo true, 반복할 때 뒤로 되돌리기
onComplete 애니메이션이 끝났을 때 호출할 콜백함수 지정
onUpdate 애니메이션이 업데이트될 때마다 호출한 콜백함수 지정
ease 가속도 (power1, elastic, bounce,...)
stagger 타겟과 요소 애니메이션을 위한 시작 시간 지정

# 3. Tween

# 3.1. 예제

애니메이션 작업을 수행한다. 타겟(애니메이션을 적용하려는 개체), 지속 시간 및 애니메이션을 적용하려는 속성을 입력하면 간단히 애니메이션을 완성해준다. 트윈을 생성하는 매서드는 3종류가 있다.

  • gsap.to()
  • gsap.from()
  • gsap.fromTo()

css로 transform: translateX(200px) 과 같은 효과를 주며 duration은 미작성시 0.5초 이다

미리보기
https://qwerewqwerew.github.io/source/js/deep/gsap/gsap01.html

<head>
	<style>
		.box {
			margin: 15px;
		}
		.box1 {
			width: 100px;
			height: 100px;
			background: orange;
		}
		.box2 {
			width: 200px;
			height: 100px;
			background: greenyellow;
		}
		.box3 {
			width: 100px;
			height: 200px;
			background: tomato;
		}
	</style>
</head>

<body>
	<div class="box box1"></div>
	<div class="box box2"></div>
	<div class="box box3"></div>

	<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.6.0/gsap.min.js"></script>
	<script>
		gsap.to('.box', {
			x: 200,
		});
	</script>
</body>

미리보기
https://qwerewqwerew.github.io/source/js/deep/gsap/gsap02.html

기본구조는 01과 같다.스크립트만 수정한다.

gsap.to('.box1', { duration: 3, x: 200, opacity: 0.2, ease: 'steps(10)', delay: 2 });
gsap.to('.box2', { duration: 3, x: 200, rotate: 720, scale: 1.3 });
gsap.to('.box3', { duration: 3, x: 200, ease: 'elastic', backgroundColor: 'red', width: 300, fontSize: 60 });

from 으로 변경하면 에니메이션이 반전되어 실행된다.

미리보기
https://qwerewqwerew.github.io/source/js/deep/gsap/gsap02a.html

gsap.from('.box1', { duration: 3, x: 200, opacity: 0.2, ease: 'steps(10)', delay: 2 });
gsap.from('.box2', { duration: 3, x: 200, rotate: 720, scale: 1.3 });
gsap.from('.box3', { duration: 3, x: 200, ease: 'elastic', backgroundColor: 'red', width: 300, fontSize: 60 });

"h2" 요소에 1초 동안 투명도를 30%로 애니메이션 적용하고 ".orange" 요소는 2초 동안 x축으로 300pixel 이동하고 ".grey"는 Y축으로 300px 이동. “.green” 요 소는 3초 동안 360도 회전하고 크기를 50%로 축소하는 애니메이션 을 적용한다

미리보기
https://qwerewqwerew.github.io/source/js/deep/gsap/2.html

<h2 class="title">gsap.to() Basic Usage</h2>
<div class="box orange"></div>
<div class="box grey"></div>
<div class="box green"></div>
body {
	margin: 10px;
}
.box {
	width: 100px;
	height: 100px;
}
.orange {
	background: orange;
}
.grey {
	background: grey;
}
.green {
	background: green;
}
gsap.to('h2.title', { duration: 1, opacity: 0.3 });
gsap.to('.orange', { duration: 2, x: 300 });
gsap.to('.grey', { duration: 2, y: 300 });
gsap.to('.green', { duration: 3, rotation: 360, scale: 0.5 });

# 4. easing

# 4.1. 예제

실행화면

<section id="title">
	<div class="box box1">none</div>
	<div class="box box2">power1</div>
	<div class="box box3">power2</div>
	<div class="box box4">power3</div>
	<div class="box box5">power4</div>
	<div class="box box6">back</div>
	<div class="box box7">elastic</div>
	<div class="box box8">bounce</div>
	<div class="box box9">rough</div>
	<div class="box box10">slow</div>
	<div class="box box11">steps</div>
	<div class="box box12">circ</div>
	<div class="box box13">expo</div>
	<div class="box box14">sine</div>
</section>
* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}

##title {
	width: 700px;
	margin: 100px auto;
}

.box {
	background-color: purple;
	width: 50px;
	height: 50px;
	border-radius: 50%;
	text-align: center;
	line-height: 50px;
	font-size: 12px;
}
gsap.to('.box1', { x: 600, ease: 'none', duration: 5 });
gsap.to('.box2', { x: 600, ease: 'power1', duration: 5 });
gsap.to('.box3', { x: 600, ease: 'power2', duration: 5 });
gsap.to('.box4', { x: 600, ease: 'power3', duration: 5 });
gsap.to('.box5', { x: 600, ease: 'power4', duration: 5 });
gsap.to('.box6', { x: 600, ease: 'back', duration: 5 });
gsap.to('.box7', { x: 600, ease: 'elastic', duration: 5 });
gsap.to('.box8', { x: 600, ease: 'bounce', duration: 5 });
gsap.to('.box9', { x: 600, ease: 'rough', duration: 5 });
gsap.to('.box10', { x: 600, ease: 'slow', duration: 5 });
gsap.to('.box11', { x: 600, ease: 'steps(5)', duration: 5 });
gsap.to('.box12', { x: 600, ease: 'circ', duration: 5 });
gsap.to('.box13', { x: 600, ease: 'expo', duration: 5 });
gsap.to('.box14', { x: 600, ease: 'sine', duration: 5 });

# 5. gsap.timeline()

타임라인 메서드는 애니메이션의 시간을 지정할수 있어 다양한 모션 구현시 편리하다.

공식문서
https://gsap.com/docs/v3/GSAP/gsap.timeline()

# 5.1. 예제

실행화면

<div class="one"></div>
<div class="two"></div>
<div class="three"></div>
<div class="four"></div>
* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}

body {
	overflow: hidden;
}

div {
	width: 100px;
	height: 100px;
}

.one {
	background: red;
}

.two {
	background: green;
}

.three {
	background: blue;
}

.four {
	background: pink;
}
let tl = gsap.timeline();
//total duration:
tl.to('.one', { duration: 2, x: 500 })
	//2초 동안 x축으로 500px

	.to('.two', { duration: 3, x: 500 }, 1)
	// 1초 후 3초동안 x축으로 500px(1은 absolute값)

	.to('.three', { duration: 1, x: 500 }, '<')
	//1초 후 1초동안 x축으로 500px(<은 이전 target의 timeline을 따라감)

	.to('.four', { duration: 1, x: 500 }, '<0.5');
//1.5초후 1초동은 x축으로 500px(<0.5를 추가하면 이전 target보다 0.5초 뒤 실행

실행화면

<section id="title">
	<div class="container">
		<h1 class="animation1">JavaScript GSAP Library Animation</h1>
		<p class="animation1">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore, dolore.</p>
		<a class="animation1" href="#">Button</a>
	</div>
</section>
<section>
	<div class="box box1"></div>
	<div class="box box2"></div>
</section>
<section id="thumbnail">
	<img src="http://placekitten.com/200/300" />
</section>
* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}

body {
	overflow: hidden;
}

#title {
	width: 700px;
	margin: 100px auto;
}

.container {
	padding: 20px;
}

h1 {
	margin-bottom: 20px;
}

p {
	margin-bottom: 15px;
}

a {
	display: block;
	width: 100px;
	padding: 10px;
	background-color: blueviolet;
	text-decoration: none;
	color: white;
	text-align: center;
	border-radius: 10px;
}

#thumbnail {
	width: 420px;
	height: 300px;
	position: absolute;
	right: 0;
	bottom: 0;
}
.box {
	width: 100px;
	height: 100px;
}

.box1 {
	background-color: red;
}

.box2 {
	background-color: blue;
}
let t1 = gsap.timeline({ defaults: { duration: 1 } });
//defaults라는 특수 속성은 모든 하위 트윈과 타임라인에서 값을 상속할 수 있다.
//애니메이션을 1초동안 진행하는 기본값 설정

t1.from('h1', { y: -50, opacity: 0 })
	.from('p', { y: -50, opacity: 0 }, '-=0.5') // 타임라인 종료 0.5초 전 (오버랩)
	.from('a', { y: -50, opacity: 0 }, '+=1') // 타임라인 종료 1초 후 (갭)
	.from('img', { y: 200, opacity: 0 }, '3'); // 타임라인 시작으로부터 3초 후 (절대적)
	.from('.box1', { x: 200, opacity: 0 }, "<") // 이전 트윈 타임라인 시작지점
	.to('.box2', { rotate: 360, x:800, opacity: 1}, ">") // 이전 트윈 타임라인 종료지점

실행화면

문서의 구조와 스타일은 이전과 동일

let t1 = gsap.timeline({ defaults: { duration: 1 } });

t1.from('h1', { y: -50, opacity: 0 })
	.from('p', { y: -50, opacity: 0 }, '-=0.5') // 타임라인 종료 0.5초 전 (오버랩)
	.from('a', { y: -50, opacity: 0 }, '-=0.5') // 타임라인 종료 0.5초 전 (오버랩)
	.from('img', { y: 200, opacity: 0 }, '-=0.5')
	.from('.box1', { x: 200, opacity: 0 }, '<') // 이전 트윈 타임라인 시작지점
	.to('.box2', { rotate: 360, x: 800, opacity: 1 }, '>'); // 이전 트윈 타임라인 종료지점

document.getElementById('cta').addEventListener('click', (e) => {
	e.preventDefault();
	t1.reversed() ? t1.play() : t1.reverse();
	//타임라인의 진행상태가 반전 되었을경우 시작하고 아닐경우 반전
});

애니메이션의 핸들링이 궁금하다면?

실행화면

<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
		<style>
			.box {
				width: 100px;
				height: 100px;
			}

			.box1 {
				background-color: red;
			}
		</style>
	</head>

	<body>
		<div class="box box1"></div>
		<div class="nav">
			<button id="play">시작</button>
			<button id="pause">정지</button>
			<button id="resume">재개</button>
			<button id="reverse">반전</button>
			<button id="restart">재시작</button>
		</div>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.8.0/gsap.min.js"></script>
		<script>
			const tween = gsap.to(".box1", { duration: 8, x: 400, width: 400, paused: true, });

			document.querySelector("#play").onclick = function () { return tween.play(); };
			document.querySelector("#pause").onclick = function () { return tween.pause(); };
			document.querySelector("#resume").onclick = function () { return tween.resume(); };
			document.querySelector("#reverse").onclick = function () { return tween.reverse(); };
			document.querySelector("#restart").onclick = function () { return tween.restart(); }.

			//resume 애니메이션 멈춘곳 부터 재생을 다시 시작
		</script>
	</body>
</html>

# 6. scrollTrigger

스크롤트리거는 gsap 의 플러그인 으로 추가설치 해야한다.

api문서

코드펜예제

# 6.1. 주요속성

속성 설명
trigger 애니메이션이 시작되거나 끝나는 지점을 감지하는 요소이다.
start 애니메이션 트리거가 시작되는 스크롤 위치이다. 형식은 "시작점 끝점"이다.
end 애니메이션 트리거가 끝나는 스크롤 위치이다. 형식은 "시작점 끝점"이다.
scrub 스크롤 위치에 따라 애니메이션이 연동되어 재생되는지 여부이다. true로 설정하면 스크롤에 따라 애니메이션이 재생된다.
pin 특정 요소를 스크롤 동안 고정시키는 기능이다. true로 설정하면, 지정된 요소가 고정된다.
markers 시작점, 끝점, 그리고 트리거 요소의 위치를 화면에 표시하는 개발 도구이다. true로 설정하면 마커가 표시된다.
toggleActions 트리거 지점마다 애니메이션의 실행제어
• onEnter: scroll-start ~ scroll-end 사이
• onLeave : scroll-end 부분을 넘어갈 때
• onEnterBack : scroll-start ~ scroll-end 사이 재 진입시
• onLeaveBack : scroll-end부분 재 퇴장시
• 액션값: play, pause, resume, reset, restart, complete, reverse(중지된 곳부터 재생), none;
• 기본값 : toggleActions:play none none none
id ScrollTrigger 인스턴스에 고유 식별자를 부여한다. 여러 ScrollTrigger를 관리할 때 유용하다.
onEnter 스크롤이 트리거 시작 지점을 지날 때 실행할 콜백 함수이다.
onLeave 스크롤이 트리거 끝 지점을 벗어날 때 실행할 콜백 함수이다.
onEnterBack 스크롤이 트리거 끝 지점에서 시작 지점으로 돌아올 때 실행할 콜백 함수이다.
onLeaveBack 스크롤이 트리거 시작 지점을 다시 벗어날 때 실행할 콜백 함수이다.
pinSpacing pin이 활성화된 요소의 공간을 어떻게 처리할지 정의한다. "margin" 또는 "padding" 값을 사용할 수 있다.

# 6.2. 주요메서드

메서드 설명
create 새로운 ScrollTrigger 인스턴스를 생성한다. 인스턴스는 개별 애니메이션의 스크롤 트리거 설정을 관리한다.
kill ScrollTrigger 인스턴스를 제거한다. 인스턴스에 의해 생성된 변경사항들도 함께 제거된다

# 6.3. 예제

CDN
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>

실행화면

<section></section>
<section>
	<div class="box"></div>
</section>
<section></section>
.box {
	width: 200px;
	height: 200px;
	background: pink;
}

section {
	height: 100vh;
}
  • 섹션별 배경색 추가
const bg = document.querySelectorAll('section');
const colors = {
	r: 255,
	g: 255,
	b: 255,
};

bg.forEach((o, i) => {
	o.style.backgroundColor = `rgb(${colors.r - i * 100},${colors.g - i * 50},${colors.b - i * 50})`;
});
  • 스크롤트리거 작성
gsap.to('.box', {
	scrollTrigger: '.box',
	x: '50vw',
});

실행화면

  • html
<main class="cont">
	<section class="sec1 item">
		<h2 class="num">01</h2>
		<div class="box"></div>
	</section>
	<!-- //.sec1 -->
	<section class="sec2 item">
		<h2 class="num">02</h2>
		<div class="box"></div>
	</section>
	<!-- //.sec2 -->
	<section class="sec3 item">
		<h2 class="num">03</h2>
		<div class="box"></div>
	</section>
	<!-- //.sec3 -->
	<section class="sec4 item">
		<h2 class="num">04</h2>
		<div class="box"></div>
	</section>
	<!-- //.sec4 -->
	<section class="sec5 item">
		<h2 class="num">05</h2>
		<div class="box"></div>
	</section>
	<!-- //.sec5 -->
	<section class="sec6 item">
		<h2 class="num">06</h2>
		<div class="box"></div>
	</section>
	<!-- //.sec6 -->
	<section class="sec7 item">
		<h2 class="num">07</h2>
		<div class="box box7"></div>
	</section>
	<!-- //.sec7 -->
	<section class="sec8 item">
		<h2 class="num">08</h2>
		<div class="box"></div>
	</section>
	<!-- //.sec8 -->
</main>
  • css
@import url(https://qwerewqwerew.github.io/source/style/reset.css);
.cont {
	overflow: hidden;
}
.item {
	width: clamp(50%, 100%, 100%);
	height: 100vh;
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 5vw;
}
.item:nth-child(2n) {
	background-color: gray;
}
.num {
	position: absolute;
	right: 20px;
	bottom: 20px;
	font-size: 5vw;
	line-height: 1;
}
.box {
	width: 10vw;
	height: 10vw;
	background-color: #ff1515;
	background-size: cover;
	background-position: center;
}
.active.box {
	filter: hue-rotate(100deg);
}
  • js
vanilla
const box1 = document.querySelector('.sec1 .box');
const box2 = document.querySelector('.sec2 .box');
const box3 = document.querySelector('.sec3 .box');
const box4 = document.querySelector('.sec4 .box');
const box5 = document.querySelector('.sec5 .box');
const box6 = document.querySelector('.sec6 .box');
const box7 = document.querySelector('.sec7 .box');
const box8 = document.querySelector('.sec8 .box');
const box9 = document.querySelector('.sec9 .box');
  • jq
jQuery
const box1 = $('.sec1 .box');
const box2 = $('.sec2 .box');
const box3 = $('.sec3 .box');
const box4 = $('.sec4 .box');
const box5 = $('.sec5 .box');
const box6 = $('.sec6 .box');
const box7 = $('.sec7 .box');
const box8 = $('.sec8 .box');
const box9 = $('.sec9 .box');
  • gsap
// 01
gsap.to(box1, {
	duration: 2,
	x: 500,
	borderRadius: 100,
	rotation: 360,
});

// 02 : trigger
gsap.to(box2, {
	duration: 2,
	x: 500,
	rotation: 360,
	borderRadius: 100,
	backgroundColor: 'yellow',
	scrollTrigger: {
		trigger: box2,
	},
});

// 03 : toggleActions
gsap.to(box3, {
	duration: 2,
	x: 500,
	rotation: 360,
	borderRadius: 100,
	scrollTrigger: {
		trigger: box3,
		toggleActions: 'play none reverse none',
	},
});

// 04 : start, end
gsap.to(box4, {
	duration: 2,
	x: 500,
	rotation: 360,
	borderRadius: 100,
	scrollTrigger: {
		trigger: box4,
		start: 'top 50%',
		end: 'bottom 20%',
		toggleActions: 'play none reverse none',
		markers: true,
	},
});

// 05 : scrub
gsap.to(box5, {
	duration: 2,
	x: 500,
	rotation: 360,
	borderRadius: 100,

	scrollTrigger: {
		trigger: box5,
		start: 'top 50%',
		end: 'bottom 20%',
		scrub: true, //true, 1, 2...
		markers: false,
	},
});

// 06 : pin
gsap.to(box6, {
	duration: 2,
	x: 500,
	rotation: 360,
	borderRadius: 100,

	scrollTrigger: {
		trigger: box6,
		start: 'top 50%',
		end: 'bottom 200px',
		scrub: true,
		pin: true,
		markers: false,
	},
});

// 07 : toggleClass
gsap.to(box7, {
	duration: 2,
	x: 500,
	rotation: 360,
	borderRadius: 100,
	scrollTrigger: {
		trigger: box7,
		start: 'top center',
		end: 'bottom 20%',
		scrub: true,
		toggleClass: 'active',
		markers: false,
		id: 'box7',
	},
});

// 08 : callback
gsap.to(box8, {
	duration: 2,
	x: 500,
	rotation: 360,
	borderRadius: 100,

	scrollTrigger: {
		trigger: box8,
		start: 'top center',
		end: 'bottom 20%',
		scrub: true,
		markers: true,
		// onEnter: () => {console.log("onEnter")},
		// onLeave: () => {console.log("onLeave")},
		// onEnterBack: () => {console.log("onEnterBack")},
		// onLeaveBack: () => {console.log("onLeaveBack")},
		onUpdate: (self) => {
			console.log('onUpdate', self.progress.toFixed(3));
		},
		onToggle: (self) => {
			console.log('onToggle', self.isActive);
			myAni();
		},
	},
});
function myAni() {
	const txt = '<span>1</span>';
	box8.parent().append(txt);
}

실행화면

  1. html 08번 예제와 유사하나 일부 요소가 추가된다.
<section class="sec2 item">
	<h2 class="num">02</h2>
	<div class="box i1"></div>
	<div class="box i2"></div>
	<div class="box i3"></div>
</section>
<!-- //.sec2 -->
<section class="sec3 item">
	<h2 class="num">03</h2>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
	<div class="box"></div>
</section>
<!-- //.sec3 -->
<section class="sec4 item">
	<h2 class="num">04</h2>
	<div class="box"></div>
</section>
<!-- //.sec4 -->
<section class="sec5 item">
	<h2 class="num">05</h2>
	<div class="text t1">Lorem</div>
	<div class="text t2">Lorem</div>
	<div class="text t3">Lorem</div>
	<div class="text t4">Lorem</div>
</section>
<!-- //.sec5 -->
<section class="sec6 item">
	<h2 class="num">06</h2>
	<div class="text">Lorem</div>
</section>
<!-- //.sec6 -->
<section class="sec7 item">
	<h2 class="num">07</h2>
	<div class="text t1">Lorem</div>
	<div class="text t2">Ipsum</div>
	<div class="text t3">Gsap</div>
	<div class="text t4">HTML</div>
	<div class="text t5">CSS</div>
	<div class="text t6">Javascript</div>
	<div class="text t7">Jquery</div>
</section>
<!-- //.sec7 -->
<section class="sec8 item">
	<h2 class="num">08</h2>
	<div class="text t1">Lorem</div>
	<div class="text t2">Ipsum</div>
	<div class="text t3">Gsap</div>
	<div class="box"></div>
</section>
<!-- //.sec8 -->
<section class="sec9 item">
	<h2 class="num">09</h2>
	<div class="box"></div>
</section>
<!-- //.sec9 -->
  1. css 추가
.sec4 .box,
.sec9 .box {
	background: url(http://qwerew.cafe24.com/images/1.jpg) no-repeat center center/100%;
}
.text {
	font-size: 10rem;
}
.sec5.item {
	flex-direction: column;
}
.sec7 .text {
	position: absolute;
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
	background-color: #fff;
	width: 100%;
	padding: 2rem;
	text-align: center;
}
  1. js
//01 : 이미지 애니메이션 주기
const ani1 = gsap.timeline();
ani1.to('.sec1 .box', { rotation: 450, scale: 0, borderRadius: 200 }).to('.sec1 .box', { rotation: 360 * 5, scale: 1, borderRadius: 20 });
ScrollTrigger.create({
	animation: ani1,
	trigger: '.sec1',
	start: 'top top',
	end: '+=2000', //시작 부분부터 2000px까지 스크롤 한 후종료
	scrub: true,
	pin: true, //화면고정 //true | 요소이름
	anticipatePin: 1,
	markers: true,
	id: 'sec1',
});

//02 : 순차 나타나기
const ani2 = gsap.timeline();
ani2.from('.sec2 .i1', { y: 200, autoAlpha: 0 }).from('.sec2 .i2', { y: 100, autoAlpha: 0 }).from('.sec2 .i3', { y: -100, autoAlpha: 0 });

ScrollTrigger.create({
	animation: ani2,
	trigger: '.sec2',
	start: 'top top',
	end: '+=2000',
	scrub: true,
	pin: true,
	anticipatePin: 1,
	markers: true,
});

//03 : 랜덤
const ani3 = gsap.timeline();
ani3.from('.sec3 .box', {
	y: -300,
	scale: 0.5,
	autoAlpha: 0,
	ease: 'back.out(4)',
	//stagger: 0.3, // 0.3초 간격으로 실행
	stagger: {
		amount: 3, //총 3초 동안 무작위
		from: 'random',
	},
});

ScrollTrigger.create({
	animation: ani3,
	trigger: '.sec3',
	start: 'top top',
	end: '+=2000',
	scrub: true,
	pin: true,
	anticipatePin: 1,
	markers: true,
});

//04 : 이미지 축소하기
const ani4 = gsap.timeline();
ani4.from('.sec4 .box', {
	autoAlpha: 0,
	scale: 5,
	width: '100vw',
	height: '100vh',
});

ScrollTrigger.create({
	animation: ani4,
	trigger: '.sec4',
	start: 'top top',
	end: '+=3000',
	scrub: true,
	pin: true,
	anticipatePin: 1, //1초전 //핀이 시작되기 직전에 애니메이션을 미리 시작해서 예상치 못한 끊김 방지
	markers: true,
});

//05 : 텍스트 애니메이션
const ani5 = gsap.timeline();

//"-=1" 1초전 시작
ani5.to('.sec5 .t1', { xPercent: 300 }).to('.sec5 .t2', { xPercent: -300 }, '-=1').to('.sec5 .t3', { xPercent: 300 }).to('.sec5 .t4', { xPercent: -300 });

//'text' 그룹핑기능 함께 동시실행

//ani5.to('.sec5 .t1', { xPercent: 300 }).to('.sec5 .t2', { xPercent: -300 }, 'text').to('.sec5 .t3', { xPercent: 300 }, 'text').to('.sec5 .t4', { xPercent: -300 }, 'text');

ScrollTrigger.create({
	animation: ani5,
	trigger: '.sec5',
	start: 'top top',
	end: '+=3000',
	scrub: true,
	pin: true,
	anticipatePin: 1,
	markers: true,
});

//06 : 텍스트 확대하기
const ani6 = gsap.timeline();
ani6.to('.sec6 .text', { scale: 60, duration: 2 }).to('.sec6 .text', { autoAlpha: 0 });

ScrollTrigger.create({
	animation: ani6,
	trigger: '.sec6',
	start: 'top top',
	end: '+=4000',
	scrub: true,
	pin: true,
	anticipatePin: 1,
	markers: true,
});

//07 : 텍스트 제자리 애니메이션
const ani7 = gsap.timeline();
ani7.from('.sec7 .t1', { autoAlpha: 0, duration: 1, y: 50 }, '+=1').from('.sec7 .t2', { autoAlpha: 0, duration: 1, y: 50 }, '+=1').from('.sec7 .t3', { autoAlpha: 0, duration: 1, y: 50 }, '+=1').from('.sec7 .t4', { autoAlpha: 0, duration: 1, y: 50 }, '+=1').from('.sec7 .t5', { autoAlpha: 0, duration: 1, y: 50 }, '+=1').from('.sec7 .t6', { autoAlpha: 0, duration: 1, y: 50 }, '+=1').from('.sec7 .t7', { autoAlpha: 0, duration: 1, y: 50 }, '+=1');

ScrollTrigger.create({
	animation: ani7,
	trigger: '.sec7',
	start: 'top top',
	end: '+=6000',
	scrub: true,
	pin: true,
	anticipatePin: 1,
	markers: true,
});

//08 : 텍스트 애니메이션
const ani8 = gsap.timeline();
ani8
	.from('.sec8 .t1', { x: innerWidth * 1 })
	.from('.sec8 .t2', { x: innerWidth * -1 })
	.from('.sec8 .t3', { x: innerWidth * 1 })
	.from('.sec8 .box', { x: innerWidth * 1, rotation: 360, scale: 5.5 });

ScrollTrigger.create({
	animation: ani8,
	trigger: '.sec8',
	start: 'top top',
	end: '+=4000',
	scrub: true,
	pin: true,
	anticipatePin: 1,
	markers: true,
});

//09 : 이미지 확대하기
const ani9 = gsap.timeline();
ani9.to('.sec9 .box', { scale: 13 }).to('.sec9 .box', { autoAlpha: 0 });

ScrollTrigger.create({
	animation: ani9,
	trigger: '.sec9',
	start: 'top top',
	end: '+=4000',
	scrub: true,
	pin: true,
	markers: false,
	anticipatePin: 1,
});

anticipatePin : 핀이 시작되기 전 애니메이션을 미리 시작해서 예상치 못한 끊김 방지 (1은 1초전 시작)

실행화면

  1. html 작성
<main class="cont">
	<!-- section.item.sec$*5>h2.num+.box -->
	<section class="item sec1">
		<h2 class="num">1</h2>
		<div class="box"></div>
	</section>
	<section class="item sec2">
		<h2 class="num">2</h2>
		<div class="box"></div>
	</section>
	<section class="item sec3">
		<h2 class="num">3</h2>
		<div class="box"></div>
	</section>
	<section class="item sec4">
		<h2 class="num">4</h2>
		<div class="box"></div>
	</section>
	<section class="item sec5">
		<h2 class="num">5</h2>
		<div class="box"></div>
	</section>
</main>
  1. css 추가
.box {
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100vh;
	background-color: #fff;
	background-size: cover;
	background-position: center;
}
.box::after {
	content: '';
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: 100vh;
	background-color: rgba(0, 0, 0, 0.7);
	z-index: 1;
}
.num {
	position: absolute;
	font-size: 10vw;
	z-index: 10;
	color: #fff;
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
}
.sec1 .box {
	background-image: url(http://qwerew.cafe24.com/images/1.jpg);
}
.sec2 .box {
	background-image: url(http://qwerew.cafe24.com/images/2.jpg);
}
.sec3 .box {
	background-image: url(http://qwerew.cafe24.com/images/3.jpg);
}
.sec4 .box {
	background-image: url(http://qwerew.cafe24.com/images/4.jpg);
}
.sec5 .box {
	background-image: url(http://qwerew.cafe24.com/images/5.jpg);
}
  1. js
// 01. 한개 섹션 고정시키기

function fn1() {
	const panel = document.querySelector('.sec5');

	ScrollTrigger.create({
		trigger: panel,
		start: 'top top',
		pin: true,
		pinSpacing: false,
	});
}
// 02. 여러개 섹션 고정시키기
function fn2() {
	gsap.utils.toArray('.box').forEach((panel, i) => {
		ScrollTrigger.create({
			trigger: panel,
			start: 'top top',
			pin: true,
			pinSpacing: false,
		});
	});
}
// 03. 스냅 고정
function fn3() {
	let panels = gsap.utils.toArray('.box');
	let tops = panels.map((panel) => ScrollTrigger.create({ trigger: panel, start: 'top top' }));
	console.log(tops);
	panels.forEach((panel, i) => {
		ScrollTrigger.create({
			trigger: panel,
			start: () => (panel.offsetHeight < window.innerHeight ? 'top top' : 'bottom bottom'),
			pin: true,
			pinSpacing: false,
		});
	});

	ScrollTrigger.create({
		snap: {
			snapTo: (progress, self) => {
				let panelStarts = tops.map((st) => st.start),
					snapScroll = gsap.utils.snap(panelStarts, self.scroll());
				return gsap.utils.normalize(0, ScrollTrigger.maxScroll(window), snapScroll);
			},
			duration: 0.5,
		},
	});
}
fn3();

실행화면

  1. html
<main class="cont">
	<!-- section.item.sec$*9>h2.num+.box -->
	<section class="item sec1">
		<h2 class="num">1</h2>
		<div class="img_wrap">
			<div class="img"></div>
		</div>
		<div class="desc">Lorem ipsum dolor sit, amet</div>
	</section>
	<section class="item sec2">
		<h2 class="num">2</h2>
		<div class="img_wrap">
			<div class="img"></div>
		</div>
		<div class="desc">Lorem ipsum dolor sit, amet</div>
	</section>
	<section class="item sec3">
		<h2 class="num reveal">3</h2>
		<div class="img_wrap">
			<div class="img "></div>
		</div>
		<div class="desc reveal ltr" data-delay="0.8">Lorem ipsum dolor sit, amet</div>
		<div class="desc reveal ttb" data-delay="0.3">Lorem ipsum dolor sit, amet</div>
	</section>
	<section class="item sec4">
		<h2 class="num">4</h2>
		<div class="img_wrap reveal">
			<div class="img"></div>
		</div>
		<div class="desc reveal btt" data-delay="0.5">Lorem ipsum dolor sit, amet</div>
	</section>
</main>
  1. css
section.item {
	padding-top: 5vw;
	margin: 10vw 0;
	margin-right: 0;
	text-align: left;
}
.img_wrap {
	position: relative;
	width: 100%;
	overflow: hidden;
	/* 1080/1920*100 */
	padding-bottom: 50%;
}
.img {
	position: absolute;
	filter: grayscale(100%);
	transition: all 1s;
	width: 100%;
	height: 100%;
	background-repeat: no-repeat;
	background-position: center center;
	background-size: cover;
}
.img_wrap:hover .img {
	filter: grayscale(0%);
	transform: scale(1.3);
}
.num {
	position: absolute;
	font-size: 10vw;
	z-index: 10;
	color: #fff;
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
}
.desc {
	font-size: 5vw;
	font-weight: 900;
	margin-left: -50%;
	margin-top: 50%;
	z-index: 100;
	position: relative;
	-webkit-text-fill-color: #0000802d;
	-webkit-text-stroke: 1px #000080;
	text-stroke: 1px 000080;
}
.desc:before {
	content: 'Lorem ipsum dolor sit, amet';
	position: absolute;
	filter: drop-shadow(-15px -10px 10px rgba(28, 33, 106, 0.5)) drop-shadow(10px 10px 10px rgba(13, 16, 65, 0.8)) drop-shadow(20px 20px 40px rgba(9, 11, 41, 0.8));
}
.ttb {
	margin-top: 0%;
}
.sec1 .img {
	background-image: url(http://qwerew.cafe24.com/images/1.jpg);
}
.sec2 .img {
	background-image: url(http://qwerew.cafe24.com/images/2.jpg);
}
.sec3 .img {
	background-image: url(http://qwerew.cafe24.com/images/3.jpg);
}
.sec4 .img {
	background-image: url(http://qwerew.cafe24.com/images/4.jpg);
}
  1. js
//01. 패럴렉스1개
const fn1 = () => {
	gsap.to('.desc', {
		yPercent: -100,
		ease: 'none',
		duration: 0.5,
		scrollTrigger: {
			trigger: '.desc',
			start: 'top bottom',
			end: 'bottom top',
			markers: true,
			scrub: true,
		},
	});
};

//02. 패럴렉스여러개
const fn = () => {
	gsap.utils.toArray('.desc').forEach((item) => {
		gsap.to(item, {
			yPercent: -200,
			ease: 'none',
			duration: 0.5,
			scrollTrigger: {
				trigger: item,
				start: 'top bottom',
				end: 'bottom top',
				markers: false,
				scrub: 0.5,
			},
		});
	});
};
fn();

//03. 나타나기
const hide = (item) => {
	gsap.set(item, { autoAlpha: 0 });
};

const animate = (item) => {
	let x = 0;
	let y = 0;
	let delay = item.dataset.delay;

	if (item.classList.contains('ltr')) {
		(x = -100), (y = 0);
	} else if (item.classList.contains('btt')) {
		(x = 0), (y = 100);
	} else if (item.classList.contains('ttb')) {
		(x = 0), (y = -100);
	} else {
		(x = 100), (y = 0);
	}

	gsap.fromTo(item, { autoAlpha: 0, x: x, y: y }, { autoAlpha: 1, x: 0, y: 0, delay: delay, duration: 1.25, overwrite: 'auto', ease: 'expo' });
};

gsap.utils.toArray('.reveal').forEach((item) => {
	hide(item);
	ScrollTrigger.create({
		trigger: item,
		start: 'top bottom',
		end: 'bottom top',
		markers: false,
		onEnter: () => {
			animate(item);
		},
	});
});

실행화면

  1. html
<!-- nav>ul>(li>a)*9 -->
<nav>
	<h1>스크롤효과</h1>
	<ul>
		<li><a href="#sec1">sec1</a></li>
		<li><a href="#sec2">sec2</a></li>
		<li><a href="#sec3">sec3</a></li>
		<li><a href="#sec4">sec4</a></li>
		<li><a href="#sec5">sec5</a></li>
		<li><a href="#sec6">sec6</a></li>
		<li><a href="#sec7">sec7</a></li>
		<li><a href="#sec8">sec8</a></li>
		<li><a href="#sec9">sec9</a></li>
	</ul>
</nav>
<main class="cont">
	<!-- section.item#sec$*9>h2.num+.box -->
	<section class="item" id="sec1">
		<h2 class="num">1</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec2">
		<h2 class="num">2</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec3">
		<h2 class="num">3</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec4">
		<h2 class="num">4</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec5">
		<h2 class="num">5</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec6">
		<h2 class="num">6</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec7">
		<h2 class="num">7</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec8">
		<h2 class="num">8</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec9">
		<h2 class="num">9</h2>
		<div class="box"></div>
	</section>
</main>
  1. css
nav {
	position: absolute;
	display: flex;
	gap: 2rem;
	max-width: 80%;
	margin: auto;
	z-index: 9999;
	top: 0;
	transition: all 1s;
	left: 0;
	background-color: gray;
}
nav.active {
	position: fixed;
	top: 0;
	background-color: rgba(128, 128, 128, 0);
}
nav.active ul {
	padding: 0rem 8rem;
	border-radius: 50px;
	background-color: rgba(19, 116, 226, 0.5);
}
nav ul {
	display: flex;
	gap: 2rem;
	padding: 0rem 2rem;
	border-radius: 0px;
	transition: all 1s;
}
nav li {
	width: calc(100% / 9);
	height: 10rem;
	line-height: 10rem;
}
nav li a {
	font-size: 1.8rem;
	color: white;
}
nav li a.active {
	color: red;
}
  1. js
let links = gsap.utils.toArray('nav ul li a');

links.forEach((link) => {
	let element = document.querySelector(link.getAttribute('href'));
	let linkST = ScrollTrigger.create({
		trigger: element,
		start: 'top top',
	});

	ScrollTrigger.create({
		trigger: element,
		start: 'top center',
		end: 'bottom center',
		onToggle: (self) => setActive(link),
	});

	link.addEventListener('click', (e) => {
		e.preventDefault();
		gsap.to(window, { duration: 1, scrollTo: linkST.start, overwrite: 'auto' });
	});
});

function setActive(link) {
	links.forEach((el) => el.classList.remove('active'));
	link.classList.add('active');
}

ScrollTrigger.create({
	start: 'top -80',
	end: 99999,
	toggleClass: {
		className: 'active',
		targets: 'nav',
	},
});

# 7. 가로방향 스크롤

가로스크롤Forum
https://gsap.com/community/forums/topic/33311-using-gsap-scrolltrigger-for-horizontal-scroll/

gsap을 이용하여 횡스크롤을 제작해보자

cdn 추가

<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>

# 예제

실행화면

  1. html
<main class="cont">
	<h1>가로스크롤</h1>
	<section class="item" id="sec1">
		<h2 class="num">1</h2>
	</section>
	<section class="item" id="sec2">
		<h2 class="num">2</h2>
	</section>
	<section class="item" id="sec3">
		<h2 class="num">3</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec4">
		<h2 class="num">4</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec5">
		<h2 class="num">5</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec6">
		<h2 class="num">6</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec7">
		<h2 class="num">7</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec8">
		<h2 class="num">8</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec9">
		<h2 class="num">9</h2>
		<div class="box"></div>
	</section>
</main>
  1. css
main {
	overscroll-behavior: none;
	width: 900%;
	height: 100vh;
	display: flex;
	flex-wrap: nowrap;
}
  1. js
let sections = gsap.utils.toArray('section');

const move = gsap.to(sections, {
	xPercent: -100 * (sections.length - 1),
	ease: 'none',
	scrollTrigger: {
		trigger: 'main',
		pin: true,
		scrub: 1,
		snap: 1 / (sections.length - 1),
		end: '+=7000',
		//end: document.querySelector('main').offsetWidth,
	},
});

if (ScrollTrigger.isTouch > 0) {
	move;
}

실행화면

  1. html
<main class="cont">
	<h1>가로스크롤</h1>
	<section class="item" id="sec1">
		<h2 class="num">1</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec2">
		<h2 class="num">2</h2>
		<div class="box"></div>
	</section>
	<div class="horizontal">
		<section class="item" id="sec3">
			<h2 class="num">3</h2>
			<div class="box"></div>
		</section>
		<section class="item" id="sec4">
			<h2 class="num">4</h2>
			<div class="box"></div>
		</section>
		<section class="item" id="sec5">
			<h2 class="num">5</h2>
			<div class="box"></div>
		</section>
	</div>
	<section class="item" id="sec6">
		<h2 class="num">6</h2>
		<div class="box"></div>
	</section>
	<section class="item" id="sec7">
		<h2 class="num">7</h2>
		<div class="box"></div>
	</section>
</main>
  1. css
main {
	overflow: hidden;
}
.horizontal {
	display: flex;
	flex-wrap: nowrap;
	width: 500%;
}
.horizontal > section {
	width: 100%;
}
  1. js
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,
	},
});

실행화면

  1. html
<main>
	<div class="first">
		<h1>Testing horizontal scrolling w/ three sections</h1>
		<h2>First Container</h2>
	</div>
	<div class="horizontal">
		<div class="description section">
			<div>
				SCROLL DOWN
				<div class="scroll-down">
					<div class="arrow"></div>
				</div>
			</div>
		</div>
		<section class="section">
			ONE
			<div class="box-1 box">box-1</div>
		</section>
		<section class="section">
			TWO
			<div class="box-2 box">box-2</div>
		</section>
		<section class="section">
			THREE
			<div class="box-3 box">box-3</div>
		</section>
	</div>
	<div class="last">Last Container</div>
</main>
  1. css
html {
	overflow-y: scroll;
	overflow-x: hidden;
	height: 100%;
	-webkit-overflow-scrolling: touch;
	overflow-scrolling: touch;
}
.section {
	width: 100%;
	height: 100%;
	display: flex;
	justify-content: center;
	align-items: center;
	font-weight: 600;
	font-size: 10vw;
	text-align: center;
	color: rgb(66, 8, 8);
	position: relative;
	box-sizing: border-box;
	padding: 10px;
}
.horizontal {
	width: 400%;
	height: 100vh;
	display: flex;
	flex-wrap: nowrap;
}
.first {
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
	height: 100vh;
	background: yellow;
}
.last {
	display: flex;
	height: 100vh;
	background: yellow;
}
.box {
	font-size: 5vw;

	text-align: center;
	line-height: 80px;
	background-color: white;
	border-radius: 8px;
	color: #222;
	font-weight: 700;
	margin-left: -200px;
	margin-top: -200px;
	will-change: transform;
	display: flex;
	z-index: 2;
}
.box.active {
	background-color: orange;
	border: 2px solid white;
}
  1. js
let sections = gsap.utils.toArray('.section');

let scrollTween = gsap.to(sections, {
	xPercent: -100 * (sections.length - 1),
	ease: 'none',
	scrollTrigger: {
		trigger: '.horizontal',
		pin: true,
		scrub: 1,
		snap: 1 / (sections.length - 1),
		end: () => '+=' + document.querySelector('.horizontal').offsetWidth,
	},
});
gsap.set('.box-1, .box-2 ', { y: 100 });
gsap.to('.box-1', {
	y: -130,
	duration: 2,
	ease: 'elastic',
	scrollTrigger: {
		trigger: '.box-1',
		containerAnimation: scrollTween,
		start: 'left center',
		toggleActions: 'play none none reset',
		id: '1',
	},
});
gsap.to('.box-2', {
	y: -120,
	rotate: 750,
	backgroundColor: '#1e90ff',
	ease: 'none',
	scrollTrigger: {
		trigger: '.box-2',
		containerAnimation: scrollTween,
		start: 'center 80%',
		end: 'center 20%',
		scrub: true,
		id: '2',
	},
});
ScrollTrigger.create({
	trigger: '.box-3',
	containerAnimation: scrollTween,
	toggleClass: 'active',
	start: 'center 60%',
	id: '3',
	markers:true,
});

실행화면

  1. 구조작성
<div class="wrapper">
	<div class="factsContainer">
		<h2>안녕하세요</h2>
		<div class="factsContainer_sm">
			<div class="fact">
				<h3>신입 프론트엔드 김망고이다:</h3>
				<img src="https://source.unsplash.com/random" alt="" />
				<h3>일머리 좋은 신입 프론트엔드입니다</h3>
			</div>
			<div class="fact">
				<h3>좋아하는 음식</h3>
				<img src="https://source.unsplash.com/random" alt="" />
				<h3>떡순이</h3>
			</div>
			<div class="fact">
				<h3>자신있는 기술스택</h3>
				<img src="https://source.unsplash.com/random" alt="" />
				<h3>자바스크립트🤙</h3>
			</div>
			<div class="fact">
				<h3>앞으로 공부하려고 하는 기술스택</h3>
				<img src="https://source.unsplash.com/random" alt="" />
				<h3>앵귤러, 노드JS, 타입스크립트</h3>
			</div>
			<div class="fact">
				<h3>좋아하는 가수</h3>
				<img src="https://source.unsplash.com/random" alt="" />
				<h3>뉴진스 민지</h3>
			</div>
			<div class="fact">
				<h3>좋아하는 것</h3>
				<img src="https://source.unsplash.com/random" alt="" />
				<h3>강아지</h3>
			</div>
		</div>
	</div>
	<div class="socialContainer">
		<h2>저를 안뽑으시면 내일도 제생각이 나실꺼에요</h2>
	</div>
</div>
  1. css 작성
* {
	margin: 0;
	padding: 0;
}

.wrapper {
	background: #1d373f;
	overflow-x: hidden;
}

.factsContainer {
	min-height: 100vh;
	padding: 0em 2em;
	text-align: center;
	line-height: 10vh;
}

.factsContainer h2 {
	font-size: 1.8em;
	transform: scale(0);
	padding: 2em 0em;
	margin-bottom: 15vh;
	color: #f0c368;
}

.factsContainer_sm,
.factsContainer_sm1 {
	display: flex;
	width: 300vw;
}

.fact {
	display: flex;
	flex-direction: column;
	height: 40vh;
	flex: 1;
	justify-content: space-between;
	padding: 1em;
	align-items: center;
	color: #f0c368;
}

.fact img {
	width: 25vw;
	height: 100vh;
	margin: 1em;
}

.socialContainer {
	width: 100vw;
	height: 100vh;
	color: white;
	font-size: 5em;
}
  1. js
//애니메이션 해야할 대상이 많으므로 전체 타임라인에 부모요소를 추가한다
let scroll_tl = gsap.timeline({
	scrollTrigger: {
		trigger: '.factsContainer',
		markers: true,
		start: 'top top', //시작점 설정 윗방향기준 뷰포드 중앙에서 시작
		end: '+=300', //300px 떨어진거리에서 끝
		scrub: true,
	},
});
let facts = document.querySelectorAll('.fact');
let factW = document.querySelector('.factsContainer_sm').clientWidth;
console.log(factW);
scroll_tl.to('.factsContainer h2', {
	scale: 1.5,
	duration: 1,
	ease: 'slow',
});
scroll_tl.to(facts, {
	xPercent: -85 * (facts.length - 1), //x이동거리
	scrollTrigger: {
		trigger: '.factsContainer_sm',
		start: 'center center',
		pin: true,
		scrub: 1,
		snap: 1 / (facts.length - 1),
		// base vertical scrolling on how wide the container is so it feels more natural.
		// end: () => `+=${smallFactsContainer.offsetWidth}`
		end: () => `+=${factW}`,
	},
});
gsap.config({ trialWarn: false });

📢 코드설명

  • let scroll_tl=애니메이션 해야할 대상이 많으므로 전체 타임라인에 부모요소를 추가한다.

  • start = 스크롤의 시작방향과 지점 설정

  • end: 끝점 설정