Programmer's Progress

포트폴리오 사이트 프로젝트 - 1 본문

JavaScript + JQuery/Project

포트폴리오 사이트 프로젝트 - 1

Blanc et Noir 2021. 2. 10. 00:54

포트폴리오 사이트의 기본적인 형태는 One Page Scroll의 형태인데, Vertical + Horizontal 방식이다.

 

 

 

위의 One Page ScrollVertical하기 때문에 위아래로만 동작하는 것을 확인할 수 있다.

나는 이를 Horizontal하게 움직일수도 있게 설계하기 위해서 방법을 생각했고, position : fixed를 통해 이를 해결했다.

 

HTML : Source Code

<!DOCTYPE html>
<html lang="en">
<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">
    <link rel="stylesheet" href="Vertical Horizontal One Page Scroll.css">
    <script src="jquery.js"></script>
    <script src="jquery-ui.js"></script>
    <script src="Vertical Horizontal One Page Scroll.js"></script>
    <title>Document</title>
</head>
<body>
    <div class="verticalFullPage">
        <div class="horizontalFullPage verticalSelected">
            <div class="page horizontalSelected" style="background-color: red;"><div class="box"><h1 id="anime-v0-h0-i0">Programmer's progress</h1><p id="anime-v0-h0-i1">Let's study</p></div></div>
            <div class="page" style="background-color:green"><div id="box"></div></div>
        </div>
        <div class="horizontalFullPage">
            <div class="page horizontalSelected" style="background-color:rgb(124, 185, 124)"><div class="box"><h1 id="anime-v1-h0-i0">Hello World!</p></div></div>
            <div class="page" style="background-color:rgb(179, 255, 0)"><div id="box"></div></div>
            <div class="page" style="background-color:rgb(0, 68, 255)"><div id="box"></div></div>
        </div>
        <div class="horizontalFullPage">
            <div class="page horizontalSelected" style="background-color:rgb(226, 79, 255)"><div class="box"></div></div>
            <div class="page" style="background-color:rgb(0, 140, 255)"><div class="box"></div></div>
        </div>
        <div class="horizontalFullPage">
            <div class="page horizontalSelected" style="background-color:rgb(210, 255, 107)"><div class="box"></div></div>
            <div class="page" style="background-color:rgb(117, 192, 117)"><div class="box"></div></div>
            <div class="page" style="background-color:rgb(200, 163, 224)"><div class="box"></div></div>
        </div>
        <div class="horizontalFullPage">
            <div class="page horizontalSelected" style="background-color:rgb(2, 0, 128)"><div class="box"></div></div>
        </div>
        <div class="horizontalFullPage">
            <div class="page horizontalSelected" style="background-color:rgb(162, 203, 250)"><div class="box"></div></div>
            <div class="page" style="background-color:rgb(192, 54, 135)"><div class="box"></div></div>
            <div class="page" style="background-color:rgb(255, 52, 52)"><div class="box"></div></div>
        </div>
        <div class="horizontalFullPage">
            <div class="page horizontalSelected" style="background-color:rgb(23, 247, 255)"><div class="box"></div></div>
            <div class="page" style="background-color:rgb(58, 175, 58)"><div class="box"></div></div>
        </div>
        <div class="horizontalFullPage">
            <div class="page horizontalSelected" style="background-color:rgb(210, 255, 107)"><div class="box"></div></div>
            <div class="page" style="background-color:rgb(117, 192, 117)"><div class="box"></div></div>
            <div class="page" style="background-color:rgb(200, 163, 224)"><div class="box"></div></div>
        </div>
        <div class="verticalNav"><ul></ul></div>
        <table class="horizontalNav"></table>
    </div>
</body>
</html>

 

전체적으로는 내부에 <tr><td>태그가 삽입되는 <table>과 같은 구조이다.

horizontalFullPage는 내부에 page라는 기본 단위를 가지고 있으며, 이 page중 가장 첫 page는 기본적으로

horizonSelected된 상태이므로, 초기 화면이 된다. verticalSelectedhorizonFullPage도 기본적으로는 첫 페이지이다.

background-color을 일부러 주었는데 이는 제대로 동작하는지 확인하기 위해 임시로 추가했다.

최하단의 verticalNavhorizontalNav는 나중에 ul, li태그로 처리할지 table로 처리할지 고민중에 있다.

 

page에는 box클래스를 가진 div가 존재하는데, 이는 pagefixed이므로 하위 요소들의 position : absolute 사용시

위치를 정할 수 없게 되므로, relative position을 가진 div를 임시로 추가했다. 또한 하위요소들의 id를 주고, 이 id는

아래에서 소개할 animation과 관련이 있다.

 

 

CSS : Source Code

*{
    margin: 0;
    padding: 0;
}
html, body{
    width: 100%;
    height: 100%;
    position: relative;
    overflow: hidden;
}
.verticalFullPage{
    top: 0px;
    width: 100%;
    height: 100%;
    position: fixed;
}
.horizontalFullPage{
    width: 100%;
    height: 100%;
    position: fixed;
}
.page{
    width: 100%;
    height: 100%;
    position: fixed;
}
ul,li{
    list-style-type: none;
}
.horizontalNav{
    width: 100%;
    height: 15px;
    top: 100%;
    left: 50%;
    transform: translate(-50%,-100%);
    position: fixed;
}

.nav{
    background-color: white
}
.horizontalNavSelected{
    background-color: black;
}
.box{
    width: 100%;
    height: 100%;
    position: relative;
}

*[id^="anime-"]{
    position: absolute;
}

@keyframes anime-v0-h0-i0{
    0%{
        color: black;
    }
    100%{
        color: chartreuse;
    }
}
@keyframes anime-v0-h0-i1{
    0%{
        color: black;
    }
    100%{
        color: blue;
    }
}
@keyframes anime-v1-h0-i0{
    0%{
        color: black;
    }
    100%{
        color: rgb(255, 0, 0);
    }
}

#anime-v0-h0-i0{
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
}
#anime-v0-h0-i1{
    top: 55%;
    left: 50%;
    transform: translate(-50%,-55%);
}
#anime-v1-h0-i0{
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
}

@keyframes들의 이름이 상당히 특이한데 vvertical, h horizontal, iindex의 약자로 애니메이션을 동적으로

할당해주기 위해서 이런식으로 이름을 지었다. 

 

 

JavaScript : Source Code

$(document).ready(function(){
    var i,j;
    var pageWidth = parseInt($(".page").css("width"),10);
    var pageHeight = parseInt($(".page").css("height"),10);
    var verticalLength = $(".verticalFullPage > .horizontalFullPage").length;
    var horizontalLength = new Array();
    var currentVerticalIndex = 0;
    var currentHorizontalndex = 0;

    //각 행마다 가진 페이지 수 저장
    for(i=0; i<verticalLength; i++){
        horizontalLength.push($(".verticalFullPage .horizontalFullPage").eq(i).find(".page").length);
    }

    //각 페이지의 초기 위치 설정
    for(i=0; i<verticalLength; i++){
        for(j=0; j<horizontalLength[i]; j++){
            $(".horizontalFullPage").eq(i).find(".page").eq(j).css({
                "top" : (pageHeight * i)+"px",
                "left" : (pageWidth * j)+"px"
            })
        }
    }

    //수평내비게이터 생성 초기화
    for(i=0; i<horizontalLength[0]; i++){
        $(".horizontalNav").append("<td class='nav'></td>");
    }
    $(".horizontalNav td.nav").css({
        "width": (100/horizontalLength[0])+"%",
        "height":"100%"
    });
    $(".horizontalNav td").eq(0).addClass("horizontalNavSelected");

    //첫 화면 애니메이션 실행
    animateFocusedPage();

    //이벤트등록
    $(document).on("mousedown",$(".verticalFullPage"),function(e1){
        $(document).on("mousemove",$(".verticalFullPage"),function(e2){
            var i, temp =  $(".horizontalFullPage").eq(currentVerticalIndex).find(".page.horizontalSelected").index();
            if(!$(".page").is(":animated")){
                if(temp < horizontalLength[currentVerticalIndex] - 1 && e1.clientX - e2.clientX > 100){
                    $(".horizontalFullPage").eq(currentVerticalIndex).find(".page").eq(temp++).removeClass("horizontalSelected");
                    $(".horizontalFullPage").eq(currentVerticalIndex).find(".page").eq(temp).addClass("horizontalSelected");
                    currentHorizontalndex = $(".horizontalFullPage.verticalSelected").find(".page.horizontalSelected").index();
                    for(i=0; i<horizontalLength[currentVerticalIndex]; i++){
                        $(".horizontalFullPage").eq(currentVerticalIndex).find(".page").eq(i).animate({
                            "left" : (i-currentHorizontalndex)*pageWidth+"px"
                        },1000,"easeInOutExpo",reLayout);
                    }
                    $(".horizontalNav td").siblings().removeClass("horizontalNavSelected");
                    $(".horizontalNav td").eq(temp).addClass("horizontalNavSelected");
                    animateFocusedPage();
                    $(this).off("mousemove");
                }else if(temp > 0 && e2.clientX - e1.clientX > 100){
                    $(".horizontalFullPage").eq(currentVerticalIndex).find(".page").eq(temp--).removeClass("horizontalSelected");
                    $(".horizontalFullPage").eq(currentVerticalIndex).find(".page").eq(temp).addClass("horizontalSelected");
                    currentHorizontalndex = $(".horizontalFullPage.verticalSelected").find(".page.horizontalSelected").index();
                    for(i=0; i<horizontalLength[currentVerticalIndex]; i++){
                        $(".horizontalFullPage").eq(currentVerticalIndex).find(".page").eq(i).animate({
                            "left" : (i-currentHorizontalndex)*pageWidth+"px"
                        },1000,"easeInOutExpo",reLayout);
                    }
                    $(".horizontalNav td").siblings().removeClass("horizontalNavSelected");
                    $(".horizontalNav td").eq(temp).addClass("horizontalNavSelected");
                    animateFocusedPage();
                    $(this).off("mousemove");
                }else if(currentVerticalIndex < verticalLength - 1 && e1.clientY - e2.clientY > 100){
                    $(".horizontalFullPage").eq(currentVerticalIndex).removeClass("verticalSelected");
                    currentVerticalIndex++;
                    $(".horizontalFullPage").eq(currentVerticalIndex).addClass("verticalSelected");
                    for(i=0; i<verticalLength; i++){
                        $(".horizontalFullPage").eq(i).find(".page").animate({
                            "top" : (i-currentVerticalIndex)*pageHeight+"px"
                        },1000,"easeInOutExpo",reLayout);
                    }
                    animateFocusedPage();
                    horizontalNavFactory();
                    $(this).off("mousemove");
                }else if(currentVerticalIndex > 0 && e2.clientY - e1.clientY > 100){
                    $(".horizontalFullPage").eq(currentVerticalIndex).removeClass("verticalSelected");
                    currentVerticalIndex--;
                    $(".horizontalFullPage").eq(currentVerticalIndex).addClass("verticalSelected");
                    for(i=0; i<verticalLength; i++){
                        $(".horizontalFullPage").eq(i).find(".page").animate({
                            "top" : (i-currentVerticalIndex)*pageHeight+"px"
                        },1000,"easeInOutExpo",reLayout);
                    }
                    animateFocusedPage();
                    horizontalNavFactory();
                    $(this).off("mousemove");
                }
            }
        }) 
    })

    //마우스 중간에 떼면 동작 X하도록 설정
    $(document).on("mouseup",$(".verticalFullPage"),function(){
        $(this).off("mousemove");
    });

    //브라우저 리사이즈시에 각 페이지 크기 및 위치 재설정
    $(window).resize(reLayout);
    function reLayout(){
        pageWidth = parseInt($(window).width(),10);
        pageHeight = parseInt($(window).height(),10);
        var temp;
        $(this).queue(function(){
            //각 페이지 수평 위치
            for(i=0; i<verticalLength; i++){
                for(j=0; j<horizontalLength[i]; j++){
                    temp = $(".horizontalFullPage").eq(i).find(".page.horizontalSelected").index();
                    if( j != temp){
                        $(".horizontalFullPage").eq(i).find(".page").eq(j).css({
                            "left" : (j - temp)*pageWidth+"px"
                        })
                    }
                }
            }
            //각 페이지 수직 위치
            for(i=0; i<verticalLength; i++){
                temp = $(".horizontalFullPage.verticalSelected").index();
                if( i != temp){
                    $(".horizontalFullPage").eq(i).find(".page").css({
                        "top" : (i - temp)*pageHeight+"px"
                    })
                }
            }
            $(this).dequeue();
        })
    }

    //수평 내비게이터 생성 메소드
    function horizontalNavFactory(){
        $(".horizontalNav").empty(true);
        for(i=0; i<horizontalLength[currentVerticalIndex]; i++){
            $(".horizontalNav").append("<td class='nav'></td>");
        }
        $(".horizontalNav td").eq($(".horizontalFullPage.verticalSelected").find(".page.horizontalSelected").index()).addClass("horizontalNavSelected");
    }

    function animateFocusedPage(){
        currentHorizontalndex = $(".horizontalFullPage.verticalSelected").find(".page.horizontalSelected").index();
        var page =  $(".horizontalFullPage.verticalSelected").find(".page.horizontalSelected");
        var i;
        setTimeout(function(){
            $.each($("*[id^='anime-']"),function(index, obj){
                $(obj).css({
                    "animation-name" : "none"
                })
                console.log(index);
            })
            $.each($(page).find("*[id^='anime-']"),function(index,obj){
                $(obj).css({
                    "animation-name" : "anime-v"+currentVerticalIndex+"-h"+currentHorizontalndex+"-i"+index,
                    "animation-duration" : "3s",
                    "animation-fill-mode" : "both"
                });
            })
        },1000)
    }
});

 

현재 mousedownmousemove이벤트를 이용해서 PC에서 모바일 기기를 사용하는 것 마냥

스와이프 하면 페이지가 넘어가지도록 설계했다. 하지만 모바일에서도 동작하려면 다른 이벤트도 추가해야 한다.

이 고민은 나중에 해결하도록 하겠다.

 

포트폴리오 사이트 (dothome.co.kr)

 

Document

 

jrw9215.dothome.co.kr

 

'JavaScript + JQuery > Project' 카테고리의 다른 글

포트폴리오 사이트 프로젝트 - 0  (0) 2021.02.10
Comments