Blinking Hello Kitty Angel

카테고리 없음

포트폴리오 효과 추천 ! 패럴렉스 이펙트

xoouxa 2023. 5. 17. 23:33

“ 지연되는 프로젝트에 인력을 더 투입하면 오히려 더 늦어진다. ”

- Frederick Philips Brooks
Mythical Man-Month 저자
728x90

안녕하세요 ヾ(≧▽≦*)o 오늘은 포트폴리오에 넣기 좋은 효과들로 만든 패럴렉스 효과 06번을 만들어 보도록 하겠습니다!

다양한 css 기능들이 있으니 한 번 적용해 보시면 멋진 포폴이 완성될 것이라 생각합니다 ! 🍕

 

🧇 완성화면입니다.

 

HTML

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

            <section id="section2" class="parallax__item">
                <span class="parallax__item__num">02</span>
                <h2 class="parallax__item__title">section02</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc split style2">우리 모두는 인생에서 만회할 기회라 할 수 있는 큰 변화를 경험한다</p>
            </section>
            <!-- section02 -->

            <section id="section3" class="parallax__item">
                <span class="parallax__item__num">03</span>
                <h2 class="parallax__item__title">section03</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc split style3">다음에 벌어질지 모를 장벽을 걱정하여 미래를 향한 걸음을 멈춰선 안된다.</p>
            </section>
            <!-- section03 -->

            <section id="section4" class="parallax__item">
                <span class="parallax__item__num">04</span>
                <h2 class="parallax__item__title">section04</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc split style4">나 스스로가 풍요로운 사람이 되려 노력해야 한다</p>
            </section>
            <!-- section04 -->

            <section id="section5" class="parallax__item">
                <span class="parallax__item__num">05</span>
                <h2 class="parallax__item__title">section05</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc split style5">자신에 대한 평판은 신경쓰지마라</p>
            </section>
            <!-- section05 -->

            <section id="section6" class="parallax__item">
                <span class="parallax__item__num">06</span>
                <h2 class="parallax__item__title">section06</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc split style6">이상과 꿈을 버리지 마라</p>
            </section>
            <!-- section06 -->

            <section id="section7" class="parallax__item">
                <span class="parallax__item__num">07</span>
                <h2 class="parallax__item__title">section07</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc split">명사형이 아닌 동사형의 삶을 살아라</p>
            </section>
            <!-- section07 -->

            <section id="section8" class="parallax__item">
                <span class="parallax__item__num">08</span>
                <h2 class="parallax__item__title">section08</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc split">도달할 수 있는 최고의 삶과 마주하라</p>
            </section>
            <!-- section08 -->

            <section id="section9" class="parallax__item">
                <span class="parallax__item__num">09</span>
                <h2 class="parallax__item__title">section09</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc split">우리는 삶이 단 한 번 밖에 존재하지 않는단 사실을 망각하고 산다</p>
            </section>
            <!-- section09 -->
    </main>
    <!-- main -->

먼저 ,  main을 만들어줍니다. 

이 main에는 내가 원하는 사진과 글귀를 설정해 줍니다.

 


CSS

    <style>
        /* .split span{
            display: inline-block;
            min-width: 1vw;
            opacity: 0;
            transform: translateY(100px);
            transition: all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
        }
        .split.show span{
            opacity: 1;
            transform: translateY(0);
        }
        .split span:nth-child(1) {transition-delay: 100ms;}
        .split span:nth-child(2) {transition-delay: 150ms;}
        .split span:nth-child(3) {transition-delay: 200ms;}
        .split span:nth-child(4) {transition-delay: 250ms;}
        .split span:nth-child(5) {transition-delay: 300ms;}
        .split span:nth-child(6) {transition-delay: 350ms;}
        .split span:nth-child(7) {transition-delay: 400ms;}
        .split span:nth-child(8) {transition-delay: 450ms;}
        .split span:nth-child(9) {transition-delay: 500ms;}
        .split span:nth-child(10) {transition-delay: 550ms;}
        .split span:nth-child(11) {transition-delay: 600ms;}
        .split span:nth-child(12) {transition-delay: 650ms;}
        .split span:nth-child(13) {transition-delay: 700ms;}
        .split span:nth-child(14) {transition-delay: 750ms;}
        .split span:nth-child(15) {transition-delay: 800ms;}
        .split span:nth-child(16) {transition-delay: 850ms;}
        .split span:nth-child(17) {transition-delay: 900ms;}
        .split span:nth-child(18) {transition-delay: 950ms;}
        .split span:nth-child(19) {transition-delay: 1000ms;}
        .split span:nth-child(20) {transition-delay: 1050ms;}
        .split span:nth-child(21) {transition-delay: 1100ms;}
        .split span:nth-child(22) {transition-delay: 1150ms;}
        .split span:nth-child(23) {transition-delay: 1200ms;}
        .split span:nth-child(24) {transition-delay: 1250ms;}
        .split span:nth-child(25) {transition-delay: 1300ms;}
        .split span:nth-child(26) {transition-delay: 1350ms;}
        .split span:nth-child(27) {transition-delay: 1400ms;}
        .split span:nth-child(28) {transition-delay: 1450ms;}
        .split span:nth-child(29) {transition-delay: 1500ms;}
        .split span:nth-child(30) {transition-delay: 1550ms;}
        .split span:nth-child(31) {transition-delay: 1600ms;}
        .split span:nth-child(32) {transition-delay: 1650ms;}
        .split span:nth-child(33) {transition-delay: 1700ms;}
        .split span:nth-child(34) {transition-delay: 1750ms;}
        .split span:nth-child(35) {transition-delay: 1800ms;}
        .split span:nth-child(36) {transition-delay: 1850ms;}
        .split span:nth-child(37) {transition-delay: 1900ms;}
        .split span:nth-child(38) {transition-delay: 1950ms;}
        .split span:nth-child(39) {transition-delay: 2000ms;}
        .split span:nth-child(40) {transition-delay: 2050ms;} */

        /* style1 */
        /* .style1.split span {
            opacity: 0;
            display: inline-block;
            min-width: 1vw;
            transition: all 0.3s ease-in-out;
        }

        .style1.split span.show {
            opacity: 1;
        }

        /* style2 */
        /* .style2.split span {
            opacity: 0;
            display: inline-block;
            min-width: 1vw;
            transform: translateY(100px);
            transition: all 0.3s ease-in-out;
        }

        .style2.split span.show {
            opacity: 1;
            transform: translateY(0);
        } */
        /*style3  */
        /* .style3.split span {
            opacity: 0;
            display: inline-block;
            min-width: 1vw;
            transform: translateY(100px) translateX(-100px) rotate(360deg);
            transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
        } */

        /* .style3.split span.show {
            opacity: 1;
            transform: translateY(0) translateX(0) rotate(0);
        } */

        /*style4  */
        /* .style4.split span {
            opacity: 0;
            display: inline-block;
            min-width: 1vw;
        }

        .style4.split span.show {
            opacity: 1;
            animation: zoomInDown 0.5s 1;
        } */

        
        /*style5  */
        /* .style5.split span {
            opacity: 0;
            display: inline-block;
            min-width: 1vw;
        }

        .style5.split span.show {
            opacity: 1;
            animation: rubberBand 0.5s 1;
        } */

        /*style6  */
        /* .style6.split span {
            opacity: 0;
            display: inline-block;
            min-width: 1vw;
        }

        .style6.split span.show {
            opacity: 1;
            animation: bounceIn 0.5s 1;
        }

        @keyframes zoomInDown {
            0% {
                opacity: 0;
                -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
                transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
                -webkit-animation-timing-function: cubic-bezier(.55, .055, .675, .19);
                animation-timing-function: cubic-bezier(.55, .055, .675, .19)
            }

            60% {
                opacity: 1;
                -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
                transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
                -webkit-animation-timing-function: cubic-bezier(.175, .885, .32, 1);
                animation-timing-function: cubic-bezier(.175, .885, .32, 1)
            }
        }

        @keyframes rubberBand {
            0% {
                -webkit-transform: scaleX(1);
                transform: scaleX(1)
            }

            30% {
                -webkit-transform: scale3d(1.25, .75, 1);
                transform: scale3d(1.25, .75, 1)
            }

            40% {
                -webkit-transform: scale3d(.75, 1.25, 1);
                transform: scale3d(.75, 1.25, 1)
            }

            50% {
                -webkit-transform: scale3d(1.15, .85, 1);
                transform: scale3d(1.15, .85, 1)
            }

            65% {
                -webkit-transform: scale3d(.95, 1.05, 1);
                transform: scale3d(.95, 1.05, 1)
            }

            75% {
                -webkit-transform: scale3d(1.05, .95, 1);
                transform: scale3d(1.05, .95, 1)
            }

            to {
                -webkit-transform: scaleX(1);
                transform: scaleX(1)
            }
        }

        @keyframes bounceIn {
            0%,
            20%,
            40%,
            60%,
            80%,
            to {
                -webkit-animation-timing-function: cubic-bezier(.215, .61, .355, 1);
                animation-timing-function: cubic-bezier(.215, .61, .355, 1)
            }

            0% {
                opacity: 0;
                -webkit-transform: scale3d(.3, .3, .3);
                transform: scale3d(.3, .3, .3)
            }

            20% {
                -webkit-transform: scale3d(1.1, 1.1, 1.1);
                transform: scale3d(1.1, 1.1, 1.1)
            }

            40% {
                -webkit-transform: scale3d(.9, .9, .9);
                transform: scale3d(.9, .9, .9)
            }

            60% {
                opacity: 1;
                -webkit-transform: scale3d(1.03, 1.03, 1.03);
                transform: scale3d(1.03, 1.03, 1.03)
            }

            80% {
                -webkit-transform: scale3d(.97, .97, .97);
                transform: scale3d(.97, .97, .97)
            }

            to {
                opacity: 1;
                -webkit-transform: scaleX(1);
                transform: scaleX(1)
            }
            } */

            /* gsap */
            
            .split span {
                display:inline-block;
                min-width : 1vw;
                opacity: 0;
                transform: translate3d(10px , 150px, 30px) scale(2) rotate(200deg);
            }

    </style>

제가 만들고 싶은 효과는 사진이 일정한 위치에 스크롤 돼 있을 때 사진이 나오면서 글귀에 애니메이션 효과를 줘 글씨가 움직이는 효과를 줄 것입니다.

여기서 @ketframs를 사용한 이유는 , 글씨에 애니메이션 효과를 부여해 움직이는 걸 구현하기 위해 ketframs를 사용하였습니다.

 

.split span:nth-child(1) {transition-delay: 100ms;}

또한 이 코드는 글씨가 한 글자씩 움직이도록 구현해주기 위해선 글씨마다 nth-child를 지정해줘야기 때문입니다.

이 설정을 해두면 , 글씨가 한 글자씩 각각 움직이도록 설정해 두었습니다.

(하지만 ,, 글씨 양이 많다면 ,, 그건 정말 노가다 그 잡채입니다 !!!!!!!!!!)

편리하게 작업하기 위해 애초에 css에서 style을 여러개 만들어 주도록 합니다.

style마다 움직이는 효과가 다르고 모양이 다르게 작업해 주기 위해 다양한 style을 만들어 줍니다.


JAVASCRIPT

    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>
    <script>
        //텍스트 분리하기
        // let text = document.querySelector("#section1 .parallax__item__desc");
        // let splitText = text.innerText;
        // let splitWrap = splitText.split('').join('</span><span>');        
        // text.innerHTML = splitWrap = "<span>" + splitWrap + "</span>";

        //모든 텍스트 분리하기
        document.querySelectorAll(".split").forEach(text => {
            let splitText = text.innerText;
            let splitWrap = splitText.split('').join("</span><span aria-hidden='true'>");    
                splitWrap = "<span aria-hidden='true'>" + splitWrap + "</span>";
                text.innerHTML = splitWrap;
                text.setAttribute("aria-label", splitText);
        })
        
        //스크롤 이펙트

        function scroll(){
            let scrollTop = window.pageYOffset || window.scrollY;

            //css 클래스 추가(노가다로 함)
        //     document.querySelectorAll(".parallax__item").forEach(item => {
        //         if(scrollTop > item.offsetTop){
        //             item.querySelector(".split").classList.add("show");
        //         }
        // });  
        
        //span에 show를 추가
        // document.querySelectorAll(".parallax__item").forEach(item => {
        //     if(scrollTop >= item.offsetTop){
        //         item.querySelectorAll(".split span").forEach((span, i) => {
        //             setTimeout(() => {
        //                 span.classList.add("show");
        //             }, 100*i)
        //         });
        //     }
        // })

            //gsap를 이용해 작업
            const items = document.querySelectorAll(".parallax__item");

            items.forEach((item, itemIndex) => {
                    const spans = item.querySelectorAll(".split span");

                if (scrollTop >= item.offsetTop){
                    gsap.to(spans, {
                        duration:0.1,
                        opacity:1,
                        x:0,
                        y:0,
                        z:0,
                        scale:1,
                        stagger:0.1,
                        rotation:0
                    })
                }
            })

            requestAnimationFrame(scroll);
        }
        scroll();
    </script>

마지막으로 , html , css 움직이게 만들어줄 스크립트를 작성해 줍니다.
gsap를 이용해 줄 것이기 때문에 script로 gsap를 걸어줍니다.

그 다음 텍스트 한 글자마다 효과를 주었기 때문에 모든 텍스트를 분리하는 스크립트를 짜주도록 합니다.


//모든 텍스트 분리하기
document.querySelectorAll(".split").forEach(text => {
    let splitText = text.innerText;
    let splitWrap = splitText.split('').join("</span><span aria-hidden='true'>");    
        splitWrap = "<span aria-hidden='true'>" + splitWrap + "</span>";
        text.innerHTML = splitWrap;
        text.setAttribute("aria-label", splitText);
})

🍔 document.querySelectorAll(".split")은 클래스가 "split"인 모든 요소를 선택합니다.

🍔 forEach 메서드를 사용하여 선택된 각 요소에 대해 반복합니다.

🍔 text.innerText는 현재 요소의 텍스트를 가져옵니다.

🍔 splitText.split('')는 텍스트를 문자 단위로 분리한 배열을 생성합니다

🍔 .join("</span><span aria-hidden='true'>")은 배열의 각 요소를 "</span><span aria-hidden='true'>" 문자열로 연결하여 하나의 문자열로 만듭니다.

🍔 이렇게 하면 각 문자 주위에 <span> 태그가 추가되고, aria-hidden 속성이 설정됩니다."<span aria-hidden='true'>" + 🍔 splitWrap + "</span>"는 앞뒤로 <span> 태그를 추가하여 최종적인 HTML 문자열을 만듭니다.

🍔 text.innerHTML = splitWrap;은 처리된 HTML 문자열을 요소의 내부 HTML로 설정합니다.

🍔 text.setAttribute("aria-label", splitText);은 요소에 "aria-label" 속성을 추가하고, 그 값으로 원래의 텍스트를 설정합니다.


스크롤 값을 계산하는 방법 ,  스크롤 값을 화면에 띄우는 법

 function scroll(){
            let scrollTop = window.pageYOffset || window.scrollY;


            document.querySelectorAll(".parallax__item").forEach(item => {
                if(scrollTop > item.offsetTop){
                    item.querySelector(".split").classList.add("show");
                }
        });  
        

        document.querySelectorAll(".parallax__item").forEach(item => {
            if(scrollTop >= item.offsetTop){
                item.querySelectorAll(".split span").forEach((span, i) => {
                    setTimeout(() => {
                        span.classList.add("show");
                    }, 100*i)
                });
            }
        })

 

🍔scroll 함수는 페이지의 스크롤 위치를 가져옵니다.

🍔window.pageYOffset 또는 window.scrollY를 사용하여 스크롤 위치를 확인합니다.

🍔첫 번째 forEach 루프는 "parallax__item" 클래스를 가진 요소들을 선택하고, 각 요소의 위치를 확인합니다.

🍔만약 현재 스크롤 위치가 요소의 위치보다 아래에 있으면 해당 요소의 자식 요소 중 클래스가 "split"인 요소에 "show" 클래스를 추가합니다.

🍔 이를 통해 특정 스크롤 위치에 도달했을 때 해당 요소가 나타나게 할 수 있습니다.

🍔 두 번째 forEach 루프는 "parallax__item" 클래스를 가진 요소들을 선택하고, 각 요소의 위치를 확인합니다.

🍔 현재 스크롤 위치가 요소의 위치와 같거나 그 이상인 경우에 실행됩니다.

🍔 해당 요소의 자식 요소 중 클래스가 "split"이고 "span"인 요소들을 선택하고, 각각에 대해 일정한 시간 간격으로 순차적으로 "show" 클래스를 추가합니다.

🍔이를 통해 스크롤 위치에 따라 텍스트가 순차적으로 나타나게 할 수 있습니다.

🍔 setTimeout 함수를 사용하여 시간 간격을 설정하고, 100 * i는 각 요소마다 100밀리초씩 증가하는 시간 간격을 의미합니다.


마지막으로 gsap를 이용해 작업해 보도록 하겠습니다.

//gsap를 이용해 작업
            const items = document.querySelectorAll(".parallax__item");

            items.forEach((item, itemIndex) => {
                    const spans = item.querySelectorAll(".split span");

                if (scrollTop >= item.offsetTop){
                    gsap.to(spans, {
                        duration:0.1,
                        opacity:1,
                        x:0,
                        y:0,
                        z:0,
                        scale:1,
                        stagger:0.1,
                        rotation:0
                    })
                }
            })

            requestAnimationFrame(scroll);
        }
        scroll();

🍔 items 변수는 "parallax__item" 클래스를 가진 모든 요소들을 선택합니다.

🍔 forEach 루프를 사용하여 각 요소에 대해 다음 작업을 수행합니다

🍔 spans 변수는 현재 요소 내부에서 클래스가 "split"인 "span" 요소들을 선택합니다.

🍔 현재 스크롤 위치가 해당 요소의 위치보다 크거나 같은 경우에만 아래의 애니메이션을 실행합니다.

🍔 gsap.to를 사용하여 선택된 spans 요소들에 대한 애니메이션을 정의합니다.

🍔 해당 애니메이션은 0.1초 동안 실행되며, 투명도(opacity), X축 이동(x), Y축 이동(y), Z축 이동(z), 크기(scale), 애니메이션 지연(stagger), 회전(rotation) 등의 속성을 지정합니다.

🍔 이를 통해 요소들이 순차적으로 나타나고 애니메이션되는 효과를 줄 수 있습니다.

🍔 requestAnimationFrame(scroll)은 스크롤 이벤트 발생 시 scroll 함수를 호출하여 애니메이션을 계속해서 업데이트합니다.

🍔마지막으로 scroll() 함수를 호출하여 초기에도 애니메이션을 실행합니다.

 

이렇게 하면 패럴렉스 효과를 완성할 수 있습니다 ~