본문 바로가기
Tutorial/GSAP

05. GSAP Parallax Effect : 나타나기 효과

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

GSAP 패럴랙스 이펙트 : 나타나기 효과

by @webs 2023. 06. 01.
05
GSAP Parallax Effect : 나타나기 효과
난이도 쉬움

소개

안녕하세요! 웹스토리보이입니다. 이번에는 스크롤을 내리면 하나씩 나타나는 효과를 만들어 보겠습니다. 나타나기 효과는 이미지에 줄 수도 있고, 텍스트에 줄 수도 있습니다. 해당 요소에 이름에 각각 스크립트를 설정하면 조금 번거롭기 때문에 클래스 이름으로 설정할 수도 있습니다. 내가 원하는 효과의 이름을 작성하면, 그거에 맞추어 스크립트를 작성하면 됩니다. 기존과는 약간 다를 수 있지만 기본 원리는 동일하기 때문에 어렵지는 않습니다. 그럼 시작해 볼까요? 😇

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">
    <link href="https://fonts.googleapis.com/css2?family=Lato:wght@100&display=swap" 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 {
            max-width: 1600px;
            width: 98%;
            margin: 0 auto;
            /* background-color: rgba(255,255,255,0.1); */
        }
        .parallax__item {
            width: 1000px;
            max-width: 70vw;
            margin: 30vw auto;
            /* background-color: rgba(255,255,255,0.3); */
            text-align: left;
            margin-right: 0;
            position: relative;
            padding-top: 15vw;
        }
        .parallax__item:nth-child(even) {
            margin-left: 0;
            text-align: right;
        }
        .parallax__item__num {
            font-size: 35vw;
            position: absolute;
            left: -5vw;
            top: -13vw;
            opacity: 0.07;
            font-family: "Lato";
            font-weight: 100;
        }
        .parallax__item:nth-child(even) .parallax__item__num {
            left: auto;
            right: -5vw;
        }
        .parallax__item__title {
            padding-bottom: 5px;
            font-weight: 400;
        }
        .parallax__item__imgWrap {
            width: 100%;
            padding-bottom: 56.25%;
            background: #000;
            position: relative;
            overflow: hidden;
        }
        .parallax__item__img {
            position: absolute;
            left: -5%; 
            top: -5%;
            width: 110%;
            height: 110%;
            background-repeat: no-repeat;
            background-position: center center;
            background-size: cover;
            filter: saturate(0%);
            transition: all 1s;
        }
        .parallax__item__img:hover {
            filter: saturate(100%);
            transform: scale(1.025);
        }
        #section1 .parallax__item__img {
            background-image: url(assets/img/images01@2.jpg);
        }
        #section2 .parallax__item__img {
            background-image: url(assets/img/images02@2.jpg);
        }
        #section3 .parallax__item__img {
            background-image: url(assets/img/images03@2.jpg);
        }
        #section4 .parallax__item__img {
            background-image: url(assets/img/images04@2.jpg);
        }
        #section5 .parallax__item__img {
            background-image: url(assets/img/images05@2.jpg);
        }
        #section6 .parallax__item__img {
            background-image: url(assets/img/images06@2.jpg);
        }
        #section7 .parallax__item__img {
            background-image: url(assets/img/images07@2.jpg);
        }
        #section8 .parallax__item__img {
            background-image: url(assets/img/images08@2.jpg);
        }
        #section9 .parallax__item__img {
            background-image: url(assets/img/images09@2.jpg);
        }
        .parallax__item__desc {
            font-size: 4vw;
            line-height: 1.4;
            margin-top: -5vw;
            margin-left: -4vw;
            z-index: 100;
            position: relative;
            word-break: keep-all;
        }
        .parallax__item:nth-child(even) .parallax__item__desc {
            margin-left: auto;
            margin-right: -4vw;
        }
    </style>
</head>
<body>
    <header id="parallax__title">
        <h1>GSAP Parallax Effect05</h1>
        <p>GSAP scrollTrigger - 나타나기 효과</p>
        <ul>
            <li><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 class="active"><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>
            <h2 class="parallax__item__title">section1</h2>
            <figure class="parallax__item__imgWrap">
                <div class="parallax__item__img"></div>
            </figure>
            <p class="parallax__item__desc">높은 목표를 세우고, 스스로 채찍질 한다.</p>
        </section>
        <!-- //section1 -->

        <section id="section2" class="parallax__item">
            <span class="parallax__item__num">02</span>
            <h2 class="parallax__item__title">section2</h2>
            <figure class="parallax__item__imgWrap">
                <div class="parallax__item__img"></div>
            </figure>
            <p class="parallax__item__desc">결과도 중요하지만, 과정을 더 중요하게 생각한다.</p>
        </section>
        <!-- //section2 -->

        <section id="section3" class="parallax__item">
            <span class="parallax__item__num">03</span>
            <h2 class="parallax__item__title">section3</h2>
            <figure class="parallax__item__imgWrap">
                <div class="parallax__item__img"></div>
            </figure>
            <p class="parallax__item__desc">매 순간에 최선을 다하고, 끊임없이 변화한다.</p>
        </section>
        <!-- //section3 -->

        <section id="section4" class="parallax__item">
            <span class="parallax__item__num">04</span>
            <h2 class="parallax__item__title">section4</h2>
            <figure class="parallax__item__imgWrap">
                <div class="parallax__item__img"></div>
            </figure>
            <p class="parallax__item__desc">모든 일에는 기본을 중요하게 생각한다.</p>
        </section>
        <!-- //section4 -->

        <section id="section5" class="parallax__item">
            <span class="parallax__item__num">05</span>
            <h2 class="parallax__item__title">section5</h2>
            <figure class="parallax__item__imgWrap">
                <div class="parallax__item__img"></div>
            </figure>
            <p class="parallax__item__desc">열정을 잃지 않고 실패에서 실패로 걸어가는 것이 성공이다.</p>
        </section>
        <!-- //section5 -->

        <section id="section6" class="parallax__item">
            <span class="parallax__item__num">06</span>
            <h2 class="parallax__item__title">section6</h2>
            <figure class="parallax__item__imgWrap">
                <div class="parallax__item__img"></div>
            </figure>
            <p class="parallax__item__desc">천 마디 말보단 하나의 행동이 더 값지다.</p>
        </section>
        <!-- //section6 -->

        <section id="section7" class="parallax__item">
            <span class="parallax__item__num">07</span>
            <h2 class="parallax__item__title">section7</h2>
            <figure class="parallax__item__imgWrap">
                <div class="parallax__item__img"></div>
            </figure>
            <p class="parallax__item__desc">조그만 성공에 만족하지 않으며, 방심을 경계한다.</p>
        </section>
        <!-- //section7 -->

        <section id="section8" class="parallax__item">
            <span class="parallax__item__num">08</span>
            <h2 class="parallax__item__title">section8</h2>
            <figure class="parallax__item__imgWrap">
                <div class="parallax__item__img"></div>
            </figure>
            <p class="parallax__item__desc">나는 내가 더 노력할수록 운이 더 좋아진다는 걸 발견했다.</p>
        </section>
        <!-- //section8 -->

        <section id="section9" class="parallax__item">
            <span class="parallax__item__num">09</span>
            <h2 class="parallax__item__title">section9</h2>
            <figure class="parallax__item__imgWrap">
                <div class="parallax__item__img"></div>
            </figure>
            <p class="parallax__item__desc">꿈이 있다면, 그 꿈을 잡고 절대 놓아주지마라.</p>
        </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. 나타나기 표현하기

이번에는 ScrollTrigger.create를 사용하겠습니다. 다중 선택이기 때문에 gsap.utils.toArrayforEach를 사용하겠습니다. 시작점과 끝나는 점을 설정하고 애니메이션을 추가하겠습니다. 애니메이션을 추가하기 위해 콜백함수를 사용할 것입니다. onEnter 콜백함수를 이용하여 animate 함수를 만들겠습니다. GSAP의 fromTo 메서드를 이용해서 요소의 애니메이션 전과 후를 설정하겠습니다. GSAP에서 투명도는 autoAlpha를 사용합니다. 해당 부분에 오면 투명도가 작동하기 때문에 미리 투명도를 0으로 만들어야 합니다. 그래서 item.style.opacity = "0";를 설정했습니다. 이렇게 하면 자연스럽게 나타나기가 연출됩니다. 여기에 overwrite: "auto" 속성을 추가하여, 애니메이션이 한번만 작동하도록 할 것입니다. 어떤 요소에 오버 효과 애니메이션을 넣었다면 오버 했을 때 오버 효과는 짧게 한번 또는 여러번이 발생할 수 있습니다. 그럼 오버 효과 명령어는 데이터에 저장이 되는데 여러개가 저장되어 애니메이션이 계속 반복적으로 일어날 수 있습니다. 그 부분을 컨트롤 해주는 것이 overwrite 입니다. 간단하게 생각하면 애니메이션을 한번만 작동할 수 있도록 설정하는 것입니다.

gsap.utils.toArray(".parallax__item__desc").forEach((item) => {
    ScrollTrigger.create({
        trigger: item,
        start: "top 80%",
        end: "bottom 20%",
        markers: true,
        onEnter: () => {animate(item)}, 
    });

    item.style.opacity = "0";
});

const animate = (item) => {
    gsap.fromTo(item, 
        {autoAlpha: 0, x: 100, y: 0}, 
        {autoAlpha: 1, x: 0, y: 0, duration: 1.25, overwrite: "auto", ease: "expo"}
    );
}

2-2. 클래스 이름으로 설정하기

각 섹션의 텍스트 영역에 클래스 reveal을 설정했습니다.

<p class="parallax__item__desc reveal">높은 목표를 세우고, 스스로 채찍질 한다.</p>
<p class="parallax__item__desc reveal">결과도 중요하지만, 과정을 더 중요하게 생각한다.</p>
<p class="parallax__item__desc reveal">매 순간에 최선을 다하고, 끊임없이 변화한다.</p>
<p class="parallax__item__desc reveal">모든 일에는 기본을 중요하게 생각한다.</p>
<p class="parallax__item__desc reveal">열정을 잃지 않고 실패에서 실패로 걸어가는 것이 성공이다.</p>
<p class="parallax__item__desc reveal">천 마디 말보단 하나의 행동이 더 값지다.</p>
<p class="parallax__item__desc reveal">조그만 성공에 만족하지 않으며, 방심을 경계한다.</p>
<p class="parallax__item__desc reveal">나는 내가 더 노력할수록 운이 더 좋아진다는 걸 발견했다.</p>
<p class="parallax__item__desc reveal">나는 내가 더 노력할수록 운이 더 좋아진다는 걸 발견했다.</p>

선택 요소도 reveal로 변경했습니다. 이렇게 설정해도 애니메이션은 변함이 없이 똑같이 나옵니다.

gsap.utils.toArray(".reveal").forEach((item) => {
    ScrollTrigger.create({
        trigger: item,
        start: "top 80%",
        end: "bottom 20%",
        markers: true,
        onEnter: () => {animate(item)}, 
    });

    item.style.opacity = "0";
});

const animate = (item) => {
    gsap.fromTo(item, 
        {autoAlpha: 0, x: 100, y: 0}, 
        {autoAlpha: 1, x: 0, y: 0, duration: 1.25, overwrite: "auto", ease: "expo"}
    );
}

2-3. 애니메이션 위치 설정하기

애니메이션이 오른쪽에서 왼쪽으로만 나오면 이상하니, 짝수 섹션은 왼쪽에 오른쪽으로 나오게 설정할 것입니다. reveal 클래스 옆에 reveal_LTR을 설정합니다.

<p class="parallax__item__desc reveal">높은 목표를 세우고, 스스로 채찍질 한다.</p>
<p class="parallax__item__desc reveal reveal_LTR">결과도 중요하지만, 과정을 더 중요하게 생각한다.</p>
<p class="parallax__item__desc reveal">매 순간에 최선을 다하고, 끊임없이 변화한다.</p>
<p class="parallax__item__desc reveal reveal_LTR">모든 일에는 기본을 중요하게 생각한다.</p>
<p class="parallax__item__desc reveal">열정을 잃지 않고 실패에서 실패로 걸어가는 것이 성공이다.</p>
<p class="parallax__item__desc reveal reveal_LTR">천 마디 말보단 하나의 행동이 더 값지다.</p>
<p class="parallax__item__desc reveal">조그만 성공에 만족하지 않으며, 방심을 경계한다.</p>
<p class="parallax__item__desc reveal reveal_LTR">나는 내가 더 노력할수록 운이 더 좋아진다는 걸 발견했다.</p>
<p class="parallax__item__desc reveal">나는 내가 더 노력할수록 운이 더 좋아진다는 걸 발견했다.</p>

변수 xy를 설정하고, 클래스에 reveal_LTR가 있는지 체크 후 위치 값을 설정합니다. 애니메이션 처음의 위치를 변수 값으로 대처하면 상황에 따라 애니메이션을 다르게 설정할 수 있습니다.

const animate = (item) => {
    let x = 100;
    let y = 0;

    if(item.classList.contains("reveal_LTR")){
        x = -100;
        y = 0;
    }

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

2-4. 애니메이션 추가 설정하기

위에서 아래로, 아래에서 위로 애니메이션도 추가 설정하겠습니다. 이렇게 한번 만들어 놓으면, 포폴에서 편하게 사용할 수 있습니다. 이름은 reveal_BTTreveal_TTB로 설정하겠습니다.

<p class="parallax__item__desc reveal">높은 목표를 세우고, 스스로 채찍질 한다.</p>
<p class="parallax__item__desc reveal reveal_LTR">결과도 중요하지만, 과정을 더 중요하게 생각한다.</p>
<p class="parallax__item__desc reveal reveal_BTT">매 순간에 최선을 다하고, 끊임없이 변화한다.</p>
<p class="parallax__item__desc reveal reveal_LTR">모든 일에는 기본을 중요하게 생각한다.</p>
<p class="parallax__item__desc reveal reveal_TTB">열정을 잃지 않고 실패에서 실패로 걸어가는 것이 성공이다.</p>
<p class="parallax__item__desc reveal reveal_LTR">천 마디 말보단 하나의 행동이 더 값지다.</p>
<p class="parallax__item__desc reveal reveal_BTT">조그만 성공에 만족하지 않으며, 방심을 경계한다.</p>
<p class="parallax__item__desc reveal reveal_LTR">나는 내가 더 노력할수록 운이 더 좋아진다는 걸 발견했다.</p>
<p class="parallax__item__desc reveal reveal_TTB">나는 내가 더 노력할수록 운이 더 좋아진다는 걸 발견했다.</p>

변수 기본 값을 0으로 설정하고, else if문을 사용하여, 해당 변수 값을 개별적으로 설정하겠습니다. 아무것도 해당되지 않으면 기본 값이기 때문에 x=100, y=0으로 설정할 것입니다. 이렇게 하면 원하는 요소에 애니메이션을 자유롭게 설정할 수 있습니다.

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

    if(item.classList.contains("reveal_LTR")){
        x = -100;
        y = 0;
    } else if(item.classList.contains("reveal_BTT")){
        x = 0;
        y = 100;
    } else if(item.classList.contains("reveal_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, duration: 1.25, overwrite: "auto", ease: "expo"}
    );
}

2-5. 정리하기

투명도 설정을 GSPA 스타일에 맞게 따로 정리를 했습니다. 시작값과 끝나는 값을 주석표시를 하여 기본 값으로 설정하겠습니다. 그럼 섹션이 시작하면 바로 작동하게 됩니다. 이렇게 스크립트를 작성하고 원하는 곳에 reveal, reveal_LTR, reveal_BTT, reveal_TTB, data-delay="1" 을 작성하면 리빌 효과를 자유롭게 줄 수 있습니다.

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("reveal_LTR")){
        x = -100;
        y = 0;
    } else if(item.classList.contains("reveal_BTT")){
        x = 0;
        y = 100;
    } else if(item.classList.contains("reveal_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: true,
        onEnter: () => {animate(item)}, 
    });
});

3. 마무리

어떤가요? 잘 되시나요? GSAP를 이용하여 나타나기 효과를 쉽게 작성할 수 있습니다. 이렇게 한번만 만들어 놓으면 자유롭게 사용이 가능하고, 내가 원하는 기능이나 추가 옵션을 설정할 수 있습니다. 자기가 직접 스크립트를 짜고, 이해해야 수정이 가능합니다. 최대한 이해하려고 노력하고, 이해가 안되면 외우시면 됩니다. 외우다 보면 이해가 되고, 이해가 되다보면 외워집니다. 결국 반복적으로 연습하시면 됩니다. ^^ 수고하셨습니다. 🥰😍


예제 목록

댓글