JAVASCRIPT

포트폴리오 메인페이지 만들어보기 (GSAP, split)

김도현2 2023. 5. 4. 21:05
반응형

포트폴리오 메인페이지 만들어보기 (GSAP, split)

GSAP 라이브러리를 이용한 메인 페이지 일부분을 만들어봤습니다.

 

 

 

 

 

 

 

 

 

 

VSCode

<!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>포트폴리오 메인페이지</title>

    <link href="https://fonts.googleapis.com/css2?family=Abel&display=swap" rel="stylesheet">
    <style>
        @font-face {
            font-family: 'PPNeueWorld-CondensedRegular';
            font-weight: normal;
            font-style: normal;
            src: url(fonts/PPNeueWorld-CondensedRegular.woff2) format('woff2');
            font-display: swap;
        }
        @font-face {
            font-family: 'PPNeueWorld-ExtendedThin';
            font-weight: normal;
            font-style: normal;
            src: url(fonts/PPNeueWorld-ExtendedThin.woff2) format('woff2');
            font-display: swap;
        }
        @font-face {
            font-family: 'PPNeueWorld-Regular';
            font-weight: normal;
            font-style: normal;
            src: url(fonts/PPNeueWorld-Regular.woff2) format('woff2');
            font-display: swap;
        }
        @font-face {
            font-family: 'PPNeueWorld-SemiCondensedUltrabold';
            font-weight: normal;
            font-style: normal;
            src: url(fonts/PPNeueWorld-SemiCondensedUltrabold.woff2) format('woff2');
            font-display: swap;
        }
        @font-face {
            font-family: 'PPNeueWorld-SemiExtendedBlack';
            font-weight: normal;
            font-style: normal;
            src: url(fonts/PPNeueWorld-SemiExtendedBlack.woff2) format('woff2');
            font-display: swap;
        }
        @font-face {
            font-family: 'PPNeueWorld-SuperCondensedLight';
            font-weight: normal;
            font-style: normal;
            src: url(fonts/PPNeueWorld-SuperCondensedLight.woff2) format('woff2');
            font-display: swap;
        }
        @font-face {
            font-family: 'PPNeueWorld-Thin';
            font-weight: normal;
            font-style: normal;
            src: url(fonts/PPNeueWorld-Thin.woff2) format('woff2');
            font-display: swap;
        }
        * {
            padding: 0;
            margin: 0;
            box-sizing: border-box;
        }
        em {
            font-style: normal;
            font-family: 'PPNeueWorld-CondensedRegular';
        }
        body {
            width: 100%;
            height: 100vh;
            background-color: #0b130d;
            overflow: hidden;
        }
        /* header */
        #header {
            position: fixed;
            left: 0;
            top: 0;
            width: 100%;
            display: flex;
            align-items: end;
            justify-content: space-between;
            padding: 10px 20px;
            font-family: 'Abel';
            z-index: 1000;
        }
        #header h1 {
            font-weight: normal;
            color: #d2e3c0;
            font-size: 28px;
        }
        #header nav li {
            list-style: none;
            display: inline-block;
        }
        #header nav li a {
            color: #d2e3c0;
            text-transform: uppercase;
            font-weight: bold;
            padding: 10px;
            font-size: 18px;
        }

        /* main */
        #main {
            align-items: center;
            justify-content: center;
            display: flex;
            width: 100%;
            height: 100vh;
            position: relative;
        }
        .text__inner {
            color: #d2e3c0;
            text-align: center;
            position: relative;
            z-index: 3000;
        }
        .text__inner > div {
            font-size: 8vw;
            line-height: 1.2;
        }
        .text__inner > div.ti1 {
            font-family: 'PPNeueWorld-Thin';
        }
        .text__inner > div.ti2 {
            font-family: 'PPNeueWorld-SemiCondensedUltrabold';
        }
        .text__inner > div.ti3 {
            font-family: 'PPNeueWorld-ExtendedThin';
        }
        .img__inner > img {
            position: absolute;
            width: 10vw;
            z-index: 2000;
        }
        .img__inner .ii1 {
            left: 45%;
            top: 50%;
            transform: translateY(-180%);
        }
        .img__inner .ii2 {
            left: 10%;
            top: 50%;
            transform: rotate(-10deg);
        }
        .img__inner .ii3 {
            left: 80%;
            top: 50%;
            transform: translateY(-30%) rotate(20deg);
        }

        /* footer */
        #footer {
            position: fixed;
            left: 50%;
            bottom: 1vw;
            transform: translateX(-50%);
            z-index: 1000;
        }
        #footer a {
            color: #fff;
            font-family: 'Abel';
            text-decoration: none;
        }
        #footer a:hover {
            text-decoration: underline;
            text-underline-position: under;
        }
        #webgl {
            position: absolute;
            left: 0;
            top: 0;
            width: 100%;
            height: 100vh;
            z-index: 1;
        }
        #webgl iframe {
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
    <header id="header">
        <h1>KDH PORTFOLIO</h1>
        <nav>
            <ul>
                <li><a href="#">work</a></li>
                <li><a href="#">about</a></li>
            </ul>
        </nav>
    </header>
    <!-- //header -->

    <main id="main">
        <div class="text__inner">
            <div class="ti1">let's <em>introduce</em></div>
            <div class="ti2 split">frontend developer's</div>
            <div class="ti3"><em>all</em> works <em>of</em> portfolio</div>
        </div>
        <div class="img__inner">
            <img class="ii1" src="img/figure01.png" alt="이미지1">
            <img class="ii2" src="img/figure02.png" alt="이미지2">
            <img class="ii3" src="img/figure03.png" alt="이미지3">
        </div>
        <div id="webgl">
            <iframe src="three.html" frameborder="0"></iframe>
        </div>
    </main>
    <!-- //main -->

    <footer id="footer">
        <a href="https://ehcjswo.github.io/web2023/" target="_blank">https://ehcjswo.github.io/web2023/</a>
    </footer>
    <!-- //footer -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>
    <script>
        // 글씨 분리하기
        document.querySelectorAll(".split").forEach(desc => {
            let splitText = desc.innerText;
            let splitWrap = splitText.split('').join("</span><span aria-hidden='true'>");
                splitWrap = "<span aria-hidden='true'>" + splitWrap + "</span>";

                desc.innerHTML = splitWrap;
                desc.setAttribute("aria-label", splitText);

            console.log(splitWrap)
        })

        // 메인 기본세팅
        gsap.set(".text__inner .ti1", {opacity: 0, y: "20vh"})
        gsap.set(".text__inner .ti2 span", {opacity: 0, x: 500, scale:10, display: "inline-block", minWidth: "1.4vw"})
        gsap.set(".text__inner .ti3", {opacity: 0, y: "-20vh"})
        gsap.set(".img__inner .ii1", {opacity: 0, scale:50})
        gsap.set(".img__inner .ii2", {opacity: 0, scale:50})
        gsap.set(".img__inner .ii3", {opacity: 0, scale:50})
        gsap.set("#header", {opacity: 0})
        gsap.set("#footer", {opacity: 0})
        gsap.set("#webgl", {opacity: 0})

        
        // 메인 애니메이션 
        setTimeout(() => {
            let tl = gsap.timeline();


            tl.to(".text__inner .ti2 span", {opacity:1, x:0, scale:1, duration: 0.6, stagger: 0.03, ease: Power3.easeInOut})
            tl.to(".text__inner .ti1", {opacity:1, y:0, duration: 0.6, ease: Circ.easeOut}, "h +=0.2")
            tl.to(".text__inner .ti3", {opacity:1, y:0, duration: 0.6, ease: Circ.easeOut}, "h +=0.5")
            tl.to(".img__inner .ii1", {opacity:1, duration: 0.3, scale:1, ease: Circ.easeOut}, "h2 +=0.1")
            tl.to(".img__inner .ii2", {opacity:1, duration: 0.5, scale:1, ease: Circ.easeOut}, "h2 +=0.2")
            tl.to(".img__inner .ii3", {opacity:1, duration: 0.7, scale:1, ease: Circ.easeOut}, "h2 +=0.3")
            tl.to("#header", {opacity:1, duration: 10}, "h")
            tl.to("#footer", {opacity:1, duration: 12}, "h")
            tl.to("#webgl", {opacity:1, duration: 20}, "h")
        },1000);
    </script>
</body>
</html>

헤더 영역에서는 position 속성으로 fixed를 사용하여 스크롤을 내려도 항상 상단에 위치하도록 설정하고 있습니다. 그리고 display 속성으로 flex를 설정하여 요소들을 수평 방향으로 배치하고 있습니다.

 

메인 영역에서는 display 속성으로 flex를 설정하여 요소들을 수평, 수직 방향으로 배치하고 있습니다. 그리고 .text__inner 클래스와 .img__inner 클래스로 각각 텍스트와 이미지를 감싸고 있습니다. 이때 이미지는 position 속성으로 absolute를 사용하여 부모 요소를 기준으로 위치를 지정하고 있습니다.

 

푸터 영역에서는 position 속성으로 fixed를 사용하여 스크롤을 내려도 항상 하단에 위치하도록 설정하고 있습니다. transform 속성으로 translateX()를 사용하여 수평 방향으로 중앙에 위치하도록 설정하고 있습니다.'

 

 

 

 

 

script

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>
<script>
    // 글씨 분리하기
    document.querySelectorAll(".split").forEach(desc => {
        let splitText = desc.innerText;
        let splitWrap = splitText.split('').join("</span><span aria-hidden='true'>");
            splitWrap = "<span aria-hidden='true'>" + splitWrap + "</span>";

            desc.innerHTML = splitWrap;
            desc.setAttribute("aria-label", splitText);

        console.log(splitWrap)
    })

    // 메인 기본세팅
    gsap.set(".text__inner .ti1", {opacity: 0, y: "20vh"})
    gsap.set(".text__inner .ti2 span", {opacity: 0, x: 500, scale:10, display: "inline-block", minWidth: "1.4vw"})
    gsap.set(".text__inner .ti3", {opacity: 0, y: "-20vh"})
    gsap.set(".img__inner .ii1", {opacity: 0, scale:50})
    gsap.set(".img__inner .ii2", {opacity: 0, scale:50})
    gsap.set(".img__inner .ii3", {opacity: 0, scale:50})
    gsap.set("#header", {opacity: 0})
    gsap.set("#footer", {opacity: 0})
    gsap.set("#webgl", {opacity: 0})


    // 메인 애니메이션 
    setTimeout(() => {
        let tl = gsap.timeline();


        tl.to(".text__inner .ti2 span", {opacity:1, x:0, scale:1, duration: 0.6, stagger: 0.03, ease: Power3.easeInOut})
        tl.to(".text__inner .ti1", {opacity:1, y:0, duration: 0.6, ease: Circ.easeOut}, "h +=0.2")
        tl.to(".text__inner .ti3", {opacity:1, y:0, duration: 0.6, ease: Circ.easeOut}, "h +=0.5")
        tl.to(".img__inner .ii1", {opacity:1, duration: 0.3, scale:1, ease: Circ.easeOut}, "h2 +=0.1")
        tl.to(".img__inner .ii2", {opacity:1, duration: 0.5, scale:1, ease: Circ.easeOut}, "h2 +=0.2")
        tl.to(".img__inner .ii3", {opacity:1, duration: 0.7, scale:1, ease: Circ.easeOut}, "h2 +=0.3")
        tl.to("#header", {opacity:1, duration: 10}, "h")
        tl.to("#footer", {opacity:1, duration: 12}, "h")
        tl.to("#webgl", {opacity:1, duration: 20}, "h")
    },1000);
</script>

 

<script> 태그를 통해 GSAP (GreenSock Animation Platform) 라이브러리를 불러옵니다. GSAP은 HTML 요소를 쉽게 애니메이션화하는 데 도움이 되는 라이브러리입니다.

 

그 다음으로, document.querySelectorAll(".split")를 사용하여 .split 클래스를 가진 모든 요소를 선택합니다. 이 요소는 글씨를 분리해서 각 글자를 <span> 태그로 묶어주는 역할을 합니다. 이를 통해 후에 애니메이션을 적용할 때 각 글자별로 적용할 수 있습니다.

 

gsap.set() 함수를 사용하여 각 요소들의 초기 상태를 설정합니다. 요소들의 opacity, y, x, scale, display, minWidth 등의 CSS 속성을 설정합니다.

 

그 다음으로, setTimeout() 함수를 사용하여 일정 시간 후에 애니메이션을 시작합니다. 이 함수는 setTimeout(함수, 시간)으로 작성되며, 이 함수를 실행하면 시간이 지난 후에 함수를 실행합니다.

 

애니메이션은 gsap.timeline() 함수를 사용하여 타임라인을 만듭니다. tl.to() 함수를 사용하여 각 요소들을 언제 어떤 방식으로 애니메이션화할지 설정합니다. 이 함수는 tl.to(요소, 속성, 지속시간, ease, 시작시간)으로 작성됩니다. 여기서 요소는 애니메이션화할 HTML 요소이고, 속성은 애니메이션화할 CSS 속성이며, 지속시간은 애니메이션의 지속시간을 의미합니다. ease는 애니메이션의 이징(easing)을 설정하며, 시작시간은 타임라인 상에서 애니메이션이 시작되는 시간을 의미합니다.

 

마지막으로, tl 객체의 각 메서드들은 애니메이션의 순서와 지속시간을 설정합니다. stagger 속성을 사용하여 지정한 지연 시간으로 요소들을 애니메이션화할 수 있습니다.