본문 바로가기
Tutorial/GSAP

03. GSAP Parallax Effect : 배경 고정하기

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

GSAP 패럴랙스 이펙트 : 배경 고정하기

by @webs 2023. 06. 01.
03
GSAP Parallax Effect : 배경 고정하기
난이도 쉬움

소개

안녕하세요! 웹스토리보이입니다. pin 속성을 이용하여 배경을 고정하는 효과를 배워보겠습니다. 스크롤을 내리면 배경이 고정되어 있고, 다음 섹션이 올라오는 형태의 애니메이션입니다.

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;
            flex-direction: column;
        }
        #section2, 
        #section4, 
        #section6, 
        #section8 {
            background-color: #222;
        }
        .parallax__item__num {
            position: absolute;
            right: 20px;
            bottom: 20px;
            font-size: 3vw;
            line-height: 1;
            z-index: 10;
        }
        .parallax__item__img {
            position: absolute;
            left: 0;
            top: 0;
            width: 100%;
            height: 100vh;
            background-color: #fff;
            background-size: cover;
            background-position: center;
        }
        .parallax__item__img::after {
            content: '';
            position: absolute;
            left: 0;
            top: 0;
            width: 100%;
            height: 100vh;
            background-color: rgba(0,0,0,0.7);
            z-index: 1;
        }
        #section1 .parallax__item__img {
            background-image: url(assets/img/images02@2.jpg);
        }
        #section2 .parallax__item__img {
            background-image: url(assets/img/images03@2.jpg);
        }
        #section3 .parallax__item__img {
            background-image: url(assets/img/images04@2.jpg);
        }
        #section4 .parallax__item__img {
            background-image: url(assets/img/images05@2.jpg);
        }
        #section5 .parallax__item__img {
            background-image: url(assets/img/images06@2.jpg);
        }
        #section6 .parallax__item__img {
            background-image: url(assets/img/images07@2.jpg);
        }
        #section7 .parallax__item__img {
            background-image: url(assets/img/images08@2.jpg);
        }
        #section8 .parallax__item__img {
            background-image: url(assets/img/images09@2.jpg);
        }
        #section9 .parallax__item__img {
            background-image: url(assets/img/images10@2.jpg);
        }
        .parallax__item__title {
            font-size: 5vw;
            z-index: 100;
            text-transform: uppercase;
            display: inline-block;
        }
    </style>
</head>
<body>
    <header id="parallax__title">
        <h1>GSAP Parallax Effect03</h1>
        <p>GSAP scrollTrigger - 배경 고정하기</p>
        <ul>
            <li><a href="gsap01.html">1</a></li>
            <li><a href="gsap02.html">2</a></li>
            <li class="active"><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>
            <h2 class="parallax__item__title">section1</h2>
        </section>
        <!-- //section1 -->

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

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

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

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

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

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

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

        <section id="section9" class="parallax__item">
            <span class="parallax__item__num">09</span>
            <div class="parallax__item__img"></div>
            <h2 class="parallax__item__title">section9</h2>
        </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>
    <script>
    </script>
</body>
</html>

2. 스크립트 작업하기

2-1. 한개 섹션 고정시키기

현재는 9개의 섹션으로 구성되어 있습니다. 첫 번째 섹션만 고정을 시켜보도록 하겠습니다. 고정 시킬 패널을 변수에 저장하고, ScrollTrigger.create를 설정합니다. 트리거는 자기 자신으로 설정하고 pin: true를 설정합니다. 핀을 설정하면 밑에 여백이 생길 수 있는 이 부분을 제거해 주려면 pinSpacing: false로 설정합니다.

const panel = document.querySelector("#section1");

ScrollTrigger.create({
    trigger: panel,
    start: "top top",
    pin: true,
    pinSpacing: false
});

2-2. 여러개 섹션 고정시키기

이번에는 9개의 섹션의 다 적용해보겠습니다. GSAP에서는 다중 선택을 할 때에는 gsap.utils.toArray를 사용합니다. 모든 섹션의 공통 요소 parallax__item을 선택한 후 자바스크립트 forEach를 사용하면 됩니다. 나머지는 위 예제와 동일합니다.

gsap.utils.toArray(".parallax__item").forEach((panel, i) => {
    ScrollTrigger.create({
        trigger: panel,
        start: "top top",
        pin: true, 
        pinSpacing: false 
    });
});

2-3. 스냅 고정 효과 만들기

이번에는 스냅 효과를 적용하여 배경을 고정할 수 있습니다. 스크롤을 했는데 다음 섹션이 위쪽에 가까운면 위쪽으로 붙여주고, 아래쪽에 가까우면 아래쪽에 붙여주는 효과입니다. 좀 더 고급스러운 효과라고 할 수 있습니다. 그래서 소스도 조금 더 복잡합니다. forEach와 비슷한 map 메서드를 사용하여 tops 변수에 trigger와 start를 저장했습니다. 모든 패널에 스타트를 설정했는데 섹션의 offsetHeight와 브라우저의 innerHeight를 비교하여 시작점을 위로 할지 아래로 할지 정하는 소스를 설정했습니다. 그리고 스냅을 설정하기 위해 snapTo를 설정하여, 스냅의 위치를 설정하였습니다. 아까 만들어 놓은 tops 변수에 start 값을 다시 설정하고, self.scroll()를 통해 움직임을 감지합니다. gsap.utils.normalize는 값의 범위를 특정 최소값과 최대값 사이로 조정하여 일관된 비율로 변환하는 메서드입니다.

let panels = gsap.utils.toArray(".parallax__item");
let tops = panels.map(panel => ScrollTrigger.create({trigger: panel, start: "top top"}));

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
    }
});

3. 마무리

수고하셨습니다. 배경 고정 효과를 3가지 스타일로 만들어 봤습니다. 한가지만 적용하고 싶은 경우, 다중 선택을 통해 적용하는 방법 등을 배웠습니다. 이 예제를 통해 forEachmap 메서드의 차이점과 개념을 알고 계셔야 합니다. 많이 사용하니까 이해가 안되면 먼저 외우세요! 그럼 또 이해가 갑니다. 수고하셨습니다.


예제 목록

댓글