19.gsap
1. 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. 설치”2.1. cdn
섹션 제목: “2.1. cdn”<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>2.2. npm
섹션 제목: “2.2. npm”npm install gsap2.3. css transform과 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. 기타속성
섹션 제목: “2.4. 기타속성”| 속성 | 설명 |
|---|---|
| delay | 애니메이션 시작 전 지연 시간 지정 |
| repeat | 반복횟수 지정, -1(무한반복) |
| repeatDelay | 반복 전 지연 시간 지정 |
| yoyo | true, 반복할 때 뒤로 되돌리기 |
| onComplete | 애니메이션이 끝났을 때 호출할 콜백함수 지정 |
| onUpdate | 애니메이션이 업데이트될 때마다 호출한 콜백함수 지정 |
| ease | 가속도 (power1, elastic, bounce,…) |
| stagger | 타겟과 요소 애니메이션을 위한 시작 시간 지정 |
3. Tween
섹션 제목: “3. Tween”3.1. 예제
섹션 제목: “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. 이동+투명도 조절
섹션 제목: “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
섹션 제목: “4. easing”4.1. 예제
섹션 제목: “4.1. 예제”04. 가속도
섹션 제목: “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. gsap.timeline()”타임라인 메서드는 애니메이션의 시간을 지정할수 있어 다양한 모션 구현시 편리하다.
5.1. 예제
섹션 제목: “5.1. 예제”04. timeline
섹션 제목: “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
섹션 제목: “05. parallax”<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. 클릭시 반대로 진행
섹션 제목: “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
섹션 제목: “6. scrollTrigger”스크롤트리거는 gsap 의 플러그인 으로 추가설치 해야한다.
6.1. 주요속성
섹션 제목: “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. 주요메서드
섹션 제목: “6.2. 주요메서드”| 메서드 | 설명 |
|---|---|
| create | 새로운 ScrollTrigger 인스턴스를 생성한다. 인스턴스는 개별 애니메이션의 스크롤 트리거 설정을 관리한다. |
| kill | ScrollTrigger 인스턴스를 제거한다. 인스턴스에 의해 생성된 변경사항들도 함께 제거된다 |
6.3. 예제
섹션 제목: “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. 스크롤트리거 기본
섹션 제목: “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. 스크롤트리거-애니메이트
섹션 제목: “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
// 01gsap.to(box1, { duration: 2, x: 500, borderRadius: 100, rotation: 360,});
// 02 : triggergsap.to(box2, { duration: 2, x: 500, rotation: 360, borderRadius: 100, backgroundColor: 'yellow', scrollTrigger: { trigger: box2, },});
// 03 : toggleActionsgsap.to(box3, { duration: 2, x: 500, rotation: 360, borderRadius: 100, scrollTrigger: { trigger: box3, toggleActions: 'play none reverse none', },});
// 04 : start, endgsap.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 : scrubgsap.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 : pingsap.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 : toggleClassgsap.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 : callbackgsap.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. 스크롤트리거-핀
섹션 제목: “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. 스크롤트리거-핀(섹션)
섹션 제목: “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. 스크롤트리거-패럴렉스
섹션 제목: “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)
섹션 제목: “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. 가로방향 스크롤
섹션 제목: “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. 가로스크롤
섹션 제목: “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;}14. 가로+세로
섹션 제목: “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, },});15. 가로+세로+핀+애니메이션
섹션 제목: “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
섹션 제목: “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 });📢 코드설명
-
let scroll_tl=애니메이션 해야할 대상이 많으므로 전체 타임라인에 부모요소를 추가한다.
-
start = 스크롤의 시작방향과 지점 설정
-
end: 끝점 설정