19 gsap
1. gsap
gsap공식사이트 gsap코드펜 gsap치트시트 gsap의 모든 기능이 있는 cdn 링크
웹 브라우저에서 애니메이션을 구현하기 위한 자바스크립트 라이브러리 기존 CSS나 순수 자바스크립트보다 탁월한 퍼포먼스를 발휘할 수 있도록 최적화 된 애니메이션 전용 라이브러리이며, HTML5와 Flash 두 개의 플랫폼에서 사용할 수 있다. GreenSock Engaging the Internet GASP은 애니메이션을 쉽게 적용할 수 있는 라이브러리로 탁월한 퍼포먼스로 알려져 있다.
-
gsap.to() 메소드를 이용해 자연스러운 움직임을 만들 수 있다
-
다양한 종류의 애니메이션을 제공하며 필요에 따라 플러그인 형태로 추가하여 사용할수 있다. (일부유료)
-
기본 기능만을 사용할때는 gsap.js 혹은 gsap.min.js 을 사용한다.
-
문법은 camelCase 를 사용한다. ex) background-color (x), backgroundColor (O)
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(45deg) |
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초 이다
<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>
이동+회전
기본구조는 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 으로 변경하면 에니메이션이 반전되어 실행된다.
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 });
03. 이동+투명도 조절
“h2” 요소에 1초 동안 투명도를 30%로 애니메이션 적용하고 “.orange” 요소는 2초 동안 x축으로 300pixel 이동하고 “.grey”는 Y축으로 300px 이동. “.green” 요 소는 3초 동안 360도 회전하고 크기를 50%로 축소하는 애니메이션 을 적용한다
<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
참고
easing 은 가속도를 설정할수 있는 속성이다. 사용할수 있는 값은 아래와 같다. none, power1, power2, power3, power4, back, elastic, bounce, rough, slow, steps, circ, expo, sine []공식문서](https://gsap.com/docs/v3/Eases/)
4.1. 예제
04. 가속도
<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()
타임라인 메서드는 애니메이션의 시간을 지정할수 있어 다양한 모션 구현시 편리하다.
5.1. 예제
04. timeline
<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초 뒤 실행
05. parallax
참고
timeline 을 활용하여 다른 클래스의 요소들에 시차애니메이션을 적용한다.
변수 t1을 선언하고 기본값으로 1초를 delay로 할당한다.
<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}, ">") // 이전 트윈 타임라인 종료지점
06. 클릭시 반대로 진행
문서의 구조와 스타일은 이전과 동일
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 의 플러그인 으로 추가설치 해야한다.
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. 예제
<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>
07. 스크롤트리거 기본
<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',
});
08. 스크롤트리거-애니메이트
- 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
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
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);
}
09. 스크롤트리거-핀
- 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 -->
- 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;
}
- 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초전 시작)
10. 스크롤트리거-핀(섹션)
- 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>
- 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);
}
- 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();
11. 스크롤트리거-패럴렉스
- 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>
- 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);
}
- 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);
},
});
});
12. 스크롤트리거-부드러운 섹션이동(scrollTo)
- 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>
- 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;
}
- 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]
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>
예제
13. 가로스크롤
- 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>
- css
main {
overscroll-behavior: none;
width: 900%;
height: 100vh;
display: flex;
flex-wrap: nowrap;
}
- 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;
}
참고
[]isTouch] isTouch: 현재 장치의 터치 기능을 구분하는 방법로 0은 마우스/포인터만 가능(터치 없음), 1은 터치만 가능, 2는 둘 다 가능
14. 가로+세로
- 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>
- css
main {
overflow: hidden;
}
.horizontal {
display: flex;
flex-wrap: nowrap;
width: 500%;
}
.horizontal > section {
width: 100%;
}
- 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,
},
});
참고
-
10: ’+=‘는 시작 지점으로부터의 상대적 위치. horizontal.offsetWidth는 트리거 요소의 전체 너비이며, innerWidth는 브라우저 창의 내부 너비이다. (horizontal.offsetWidth - innerWidth)는 트리거 요소의 너비에서 뷰포트의 너비를 뺀 값
-
14: snap snapTo: 스크롤 중단점. 1 / (sections.length - 1) => 1/2 을 의미하며 각 섹션 사이의 상대적인 스냅 지점을 계산한다
-
15: inertia: 스크롤 중단시 미끄러짐 적용여부. false는 스크롤을 멈추면 즉시 스냅 포인트로 이동
-
16: duration: 스냅 애니메이션의 지속 시간. min과 max는 애니메이션의 최소 및 최대 지속 시간을 초 단위로 설정. 0.1로 설정되어 있어, 스냅 애니메이션의 지속 시간이 항상 0.1초로 설정함
-
17: invalidateOnRefresh: 이 속성은 ScrollTrigger가 화면 크기 변경, 디바이스 회전 등으로 인해 뷰포트가 리프레시될 때 스냅 포인트를 재계산할지 여부를 결정. true로 설정 스냅 포인트가 자동으로 업데이트되어 항상 정확한 위치에 스냅될 수 있도록 한다
15. 가로+세로+핀+애니메이션
- 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>
- 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;
}
- 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,
});
16. 포트폴리오-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>
- 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;
}
- 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 });
:::comment_box 📢 코드설명
-
let scroll_tl=애니메이션 해야할 대상이 많으므로 전체 타임라인에 부모요소를 추가한다.
-
start = 스크롤의 시작방향과 지점 설정
-
end: 끝점 설정
:::