본문 바로가기
Tutorial/GSAP

01. GSAP Parallax Effect : 기본 애니메이션

by @webstoryboy 2023. 6. 19.
Tutorial/webd

GSAP 패럴랙스 이펙트

by @webs 2023. 06. 01.
01
GSAP Parallax Effect : 기본 애니메이션
난이도 쉬움

소개

안녕하세요! 웹스토리보이입니다. 이번에는 포트폴리오 스킬을 올릴 수 있는 방법에 대해서 공부해보겠습니다. 현재 오픈되어 있는 사이트들은 패럴랙스 형태의 사이트가 많습니다. 즉 스크롤을 내리면 무엇인가 움직이는 효과들이 많습니다. 이런 효과를 패럴랙스 효과라고 합니다. 패럴랙스 파트는 자바스크립트 파트와 GSAP 파트로 나눌 수 있습니다. 기본적인 사항은 자바스크립트를 통해 익히고, 퀄리티 있는 효과들을 GSAP를 통해서 작업할 것입니다. GSAP의 scrollTrigger를 이용하면, 퀄리티 있는 효과들을 만들 수 있습니다. 그럼 같이 한번 시작해볼까요? 🥹

1. 기본 구조 만들기

1-1. 준비하기

GSAP를 배우는 시간이기 때문에 HTML/CSS 코딩은 생략하겠습니다. 기본 코딩은 복사해서 사용하겠습니다. 우선 웹폰트를 설정하고 GSAP에서 필요한 파일을 미리 셋팅해 놓겠습니다. GSAP는 자주 업데이트가 되기 때문에 제일 최선 버전을 사용하는게 좋습니다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GSAP Scroll Effect</title>

    <!-- 웹폰트 설정 -->
    <link href="https://webfontworld.github.io/NexonLv1Gothic/NexonLv1Gothic.css" rel="stylesheet">
</head>
<body>

    <!-- GSAP 라이브러리 설정 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/ScrollTrigger.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.1/ScrollToPlugin.min.js"></script>
</body>
</html>

1-2. 기본 셋팅하기

그대로 복사해서 사용해도 무방합니다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GSAP Scroll Effect</title>

    <link href="https://webfontworld.github.io/NexonLv1Gothic/NexonLv1Gothic.css" rel="stylesheet">
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        a {
            color: #fff;
            text-decoration: none;
        }
        body {
            color: #fff;
            font-family: "NexonLv1Gothic";
            font-weight: 300;
            background-color: #111;
        }
        #parallax__title {
            position: fixed;
            left: 20px;
            top: 20px;
            z-index: 1000;
        }
        #parallax__title h1 {
            font-size: 30px;
            border-bottom: 1px dashed #fff;
            margin-bottom: 10px;
            padding-bottom: 5px;
            font-weight: 400;
            display: inline-block;
        }
        #parallax__title p {
            font-size: 16px;
        }
        #parallax__title ul {
            margin-top: 10px;
        }
        #parallax__title li {
            display: inline;
        }
        #parallax__title li a {
            width: 20px; 
            height: 20px;
            border-radius: 50%;
            border: 1px dashed #fff;
            display: inline-block;
            text-align: center;
            line-height: 20px;
            font-size: 12px;
        }
        #parallax__title li.active a {
            background: #fff;
            color: #000;
        }

        /* parallax__cont */
        #parallax__cont {
            overflow: hidden;
        }
        .parallax__item {
            width: 100%;
            height: 100vh;
            position: relative;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .parallax__item:nth-child(2n){
            background-color: #222;
        }
        .parallax__item__num {
            position: absolute;
            right: 20px;
            bottom: 20px;
            font-size: 5vw;
            line-height: 1;
        }
        .parallax__item__img {
            width: 10vw;
            height: 10vw;
            background-color: #fff;
            background-size: cover;
            background-position: center;
        }
        .parallax__item:nth-child(1) .parallax__item__img {
            background-image: url(assets/img/images14.jpg);
        }
        .parallax__item:nth-child(2) .parallax__item__img {
            background-image: url(assets/img/images15.jpg);
        }
        .parallax__item:nth-child(3) .parallax__item__img {
            background-image: url(assets/img/images03.jpg);
        }
        .parallax__item:nth-child(4) .parallax__item__img {
            background-image: url(assets/img/images04.jpg);
        }
        .parallax__item:nth-child(5) .parallax__item__img {
            background-image: url(assets/img/images05.jpg);
        }
        .parallax__item:nth-child(6) .parallax__item__img {
            background-image: url(assets/img/images06.jpg);
        }
        .parallax__item:nth-child(7) .parallax__item__img {
            background-image: url(assets/img/images07.jpg);
        }
        .parallax__item:nth-child(8) .parallax__item__img {
            background-image: url(assets/img/images08.jpg);
        }
        .parallax__item:nth-child(9) .parallax__item__img {
            background-image: url(assets/img/images09.jpg);
        }
        .parallax__item__img.active {
            filter: hue-rotate(100deg);
        }
    </style>
</head>
<body>
    <header id="parallax__title">
        <h1>GSAP Parallax Effect01</h1>
        <p>GSAP scrollTrigger - 애니메이션 기본 효과</p>
        <ul>
            <li class="active"><a href="gsap01.html">1</a></li>
            <li><a href="gsap02.html">2</a></li>
            <li><a href="gsap03.html">3</a></li>
            <li><a href="gsap04.html">4</a></li>
            <li><a href="gsap05.html">5</a></li>
            <li><a href="gsap06.html">6</a></li>
            <li><a href="gsap07.html">7</a></li>
            <li><a href="gsap08.html">8</a></li>
            <li><a href="gsap09.html">9</a></li>
            <li><a href="gsap10.html">10</a></li>
            <li><a href="gsap11.html">11</a></li>
            <li><a href="gsap12.html">12</a></li>
            <li><a href="gsap13.html">13</a></li>
            <li><a href="gsap14.html">14</a></li>
            <li><a href="gsap15.html">15</a></li>
        </ul>
    </header>
    <!-- //parallax__title  -->

    <main id="parallax__cont">
        <section id="section1" class="parallax__item">
            <span class="parallax__item__num">01</span>
            <div class="parallax__item__img"></div>
        </section>
        <!-- //section1 -->

        <section id="section2" class="parallax__item">
            <span class="parallax__item__num">02</span>
            <div class="parallax__item__img"></div>
        </section>
        <!-- //section2 -->

        <section id="section3" class="parallax__item">
            <span class="parallax__item__num">03</span>
            <div class="parallax__item__img"></div>
        </section>
        <!-- //section3 -->

        <section id="section4" class="parallax__item">
            <span class="parallax__item__num">04</span>
            <div class="parallax__item__img"></div>
        </section>
        <!-- //section4 -->

        <section id="section5" class="parallax__item">
            <span class="parallax__item__num">05</span>
            <div class="parallax__item__img"></div>
        </section>
        <!-- //section5 -->

        <section id="section6" class="parallax__item">
            <span class="parallax__item__num">06</span>
            <div class="parallax__item__img"></div>
        </section>
        <!-- //section6 -->

        <section id="section7" class="parallax__item">
            <span class="parallax__item__num">07</span>
            <div class="parallax__item__img"></div>
        </section>
        <!-- //section7 -->

        <section id="section8" class="parallax__item">
            <span class="parallax__item__num">08</span>
            <div class="parallax__item__img"></div>
        </section>
        <!-- //section8 -->

        <section id="section9" class="parallax__item">
            <span class="parallax__item__num">09</span>
            <div class="parallax__item__img"></div>
        </section>
        <!-- //section9 -->
    </main>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/ScrollTrigger.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.1/ScrollToPlugin.min.js"></script>
</body>
</html>

2. 스크립트 작업하기

2-1. 기본 애니메이션

스크립트 태그를 설정하고 다음과 같이 작성합니다. 우선 선택자를 미리 만들어 놓겠습니다.

const box1 = document.querySelector("#section1 .parallax__item__img");
const box2 = document.querySelector("#section2 .parallax__item__img");
const box3 = document.querySelector("#section3 .parallax__item__img");
const box4 = document.querySelector("#section4 .parallax__item__img");
const box5 = document.querySelector("#section5 .parallax__item__img");
const box6 = document.querySelector("#section6 .parallax__item__img");
const box7 = document.querySelector("#section7 .parallax__item__img");
const box8 = document.querySelector("#section8 .parallax__item__img");
const box9 = document.querySelector("#section9 .parallax__item__img");

box1 요소를 2초동안 오른쪽으로 500px로 움직이겠습니다. 이렇게만 하면 심심하기 때문에 border-radius를 주고 회전시키겠습니다. gsap의.to 메서드를 사용하면 선택한 요소는 움직입니다. gsap의 가장 기본 애니메이션입니다.

// 01
gsap.to(box1, {
    duration: 2,
    x: 500,
    borderRadius: 100,
    rotation: 360, 
});

2-2. trigger

이번에는 스크롤을 내리고 요소가 보이는 영역에 오면 움직이도록 설정하겠습니다. 애니메이션 속성은 똑같이 작업을 하고 scrollTrigger를 설정합니다. trigger를 설정하면 스크롤을 내릴때 움직이기 시작합니다. 스크롤 이벤트를 작동시키기고 싶다면 scrollTrigger 속성을 설정하고, 움직이는 시점을 적용하기 위해서 trigger를 설정하는 것입니다. 간단하죠? 내가 움직이는 시점 타겟을 설정하면 움직입니다.

// 02 : trigger
gsap.to(box2, {
    duration: 2,
    x: 500,
    rotation: 360,
    borderRadius: 100,
    scrollTrigger: {
        trigger: box2,  //시작점 설정
    }
});

2-3. toggleActions

이번에는 toggleActions을 설정하겠습니다. 애니메이션의 행동을 설정하는 것입니다. 애니메이션이 시작했을 때, 애니메이션이 끝났을 때, 애니메이션이 시작하고 화면에 보이지 않을 때, 애니메이션이 끝나고 화면에 보이지 않을 때 4가지로 설정할 수 있습니다. onEnter, onLeave, onEnterBack, onLeaveBack를 의미하며, 여기에는 play, pause, resume, reset, restart, complete, reverse, none 요소 값을 설정할 수 있습니다. 하나씩 대입하면서 눈으로 직접확인하는 것이 가장 정확합니다.

// 03 : toggleActions
gsap.to(box3, {
    duration: 1,
    x: 500,
    rotation: 360,
    borderRadius: 100,

    scrollTrigger: {
        trigger: box3,
        toggleActions: "play pause reverse none" 
    }
});

2-4. start, end

이번에는 애니메이션이 언제 시작하는지, 끝나는지를 설정하겠습니다. trigger는 애니메이션의 기준점 역할을 하고, start는 시작점을 의미합니다. start와 end는 두가지 요소 값을 설정합니다. 첫 번째 요소는 요소의 시작점을 의미하고, 두 번째 요소는 브라우저의 시작점을 의미합니다. 요소의 시작점과 브라우저의 시작점이 만나면 애니메이션이 작동되는 원리입니다. 여기에는 top, bottom, left, right, center를 사용할 수 있으면, px이나 % 사용도 가능합니다. 여기에서 markers: true로 설정하면 마커의 위치를 확인할 수 있습니다.

// 04 : start, end
gsap.to(box4, {
    duration: 1,
    x: 500,
    rotation: 360,
    borderRadius: 100,

    scrollTrigger: {
        trigger: box4,
        start: "top 50%",
        end: "bottom 20%",
        toggleActions: "play pause reverse pause",
        markers: true,
    }
});

2-5. scrub

이번에는 scrub 속성을 알아보겠습니다. 이 속성은 스크롤을 내리면 같이 움직이게 설정할 수 있습니다. 눈으로 직접 확인하는 것이 가장 정확합니다. 기본 scrollTrigger를 설정하면, 시작점이 되었을 때 한번만 움직이지만, 이 효과는 마우스 스크롤 값에 따라 움직이도록 설정됩니다. 이 속성에는 true 및 정수 값을 넣을 수 있습니다. 미세한 차이가 있으니 직접 확인해보시면 좋을 거 같습니다.

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

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

2-6. pin

pin 속성은 고정시키는 역할을 합니다. 패럴랙스 이펙트에서 많이 쓰이는 효과입니다. 알아두면 멋있는 사이트를 만들 수 있습니다. 고정시키는 역할은 다른 예제에서 더 자세히 다룹니다. 내가 위치한 영역에 고정시키기 위해서는 pin: true를 설정합니다. 핀의 위치는 end의 두번째 속성값을 변경해보면 확인할 수 있습니다.

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

    scrollTrigger: {
        trigger: box6,
        start: "top 50%",
        end: "top 100px",
        pin: true,
        scrub: true,    
        markers: true,
    }
});

2-7. toggleClass

이번에는 toggleClass 속성을 설정해 보겠습니다. 시작점에 됐을 때 애니메이션도 줄 수 있지만 class도 추가할 수 있습니다. 미리 CSS를 만들어 놓고 확인해보겠습니다. class 이름은 active로 설정하였습니다. 여기에 id 값을 추가하면 마커의 이름을 변경할 수 있습니다.

// 07 : toggleClass
gsap.to(box7, {
    duration: 2,
    x: 500,
    rotation: 360,
    borderRadius: 100,

    scrollTrigger: {
        trigger: box7,
        start: "top center",
        end: "bottom top",
        scrub: true,    
        markers: true,
        toggleClass: "active",
        id: "box7"
    }
});

2-8. callback

이번에는 콜백함수에 대해서 알아보겠습니다. 콜백함수는 하나의 함수를 실행하고 그 다음 함수를 실행하는 함수라고 생각하면 됩니다. 보통은 두개의 함수를 실행시키면 동시에 실행되지만, 콜백함수는 하나의 함수가 실행되고, 끝나면 그 다음 함수가 실행되는 함수라고 생각하시면 이해하기가 편합니다. scrollTrigger 역시 여러가지 콜백함수를 제공하고 있습니다. toggleActions 처럼 onEnter, onLeave, onEnterBack, onLeaveBack 메서드를 제공하며, onUpdate이나 onToggle 같은 메서드도 제공합니다.

// 08 : callback
gsap.to(box8, {
    duration: 2,
    x: 500,
    rotation: 360,
    borderRadius: 100,
    
    scrollTrigger: {
        trigger: box8,
        start: "top center",
        end: "bottom 30%",
        scrub: true,    
        markers: false,
        // 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)},
    }
});

3. 마무리

이렇게 scrollTrigger의 속성 값을 알아봤습니다. 이 밖에도 더 디테일한 기능들이 있지만, 기본적인 요소를 파악한 다음 하나씩 깊게 들어가보겠습니다. 기본적인 효과를 이해하고 스크롤 이벤트를 작업한다면 조금 더 수월하게 사이트를 만들 수 있습니다. 그럼 다음 예제에서 뵙겠습니다. 수고하셨습니다. 🤕


예제 목록

댓글