본문 바로가기
Tutorial/port2023

23. 포트폴리오 사이트 만들기 : Vue-Site : 헤더 영역

by @webstoryboy 2023. 7. 31.
Tutorial/portfolio

포트폴리오 사이트 만들기 - Vue

by @webs 2023. 08. 01.
03
포트폴리오 사이트 만들기 : 헤더 영역
난이도 중간

소개

안녕하세요! 웹스토리보이입니다. 이제 기본 셋팅도 완료가 됐으니, 컴퍼넌트를 하니씩 완성해 나가겠습니다. 방법은 기존과 동일하니, 반복 연습한다고 생각하시면 됩니다.

포트폴리오 사이트 만들기

VITE SITE

  • 1. 셋팅하기
    • 1_1. vite 설치하기
    • 1_2. vite 폴더 정리하기
    • 1_3. gsap/lenis 설치하기
    • 1_4. git 연동하기
  • 2. 레이아웃
    • 2.1 레이아웃 구조 만들기
    • 2.2 메인 레이아웃 구조 만들기
    • 2.3 CSS 셋팅하기
    • 2.4 JavaScript 셋팅하기
  • 3. 헤더 영역
    • 3.1 헤더 구조 잡기
    • 3.2 헤더 디자인 설정
    • 3.3 반응형 작업하기
    • 3.4 메뉴 자바스크립트 설정
  • 4. 인트로 영역
    • 4.1 인트로 구조 잡기
    • 4.2 인트로 디자인 설정
    • 4.3 반응형 작업하기
  • 5. 스킬 영역
    • 5.1 스킬 구조 잡기
    • 5.2 스킬 디자인 설정
    • 5.3 반응형 작업하기
  • 6. 사이트 영역
    • 6.1 사이트 구조 잡기
    • 6.2 사이트 디자인 설정
    • 6.3 반응형 작업하기
  • 7. 포트폴리오 영역
    • 7.1 사이트 구조 잡기
    • 7.2 사이트 디자인 설정
    • 7.3 반응형 작업하기
    • 7.4 스크립트 작업하기
  • 8. 연락처 영역
    • 8.1 연락처 구조 잡기
    • 8.2 연락처 디자인 설정
    • 8.3 반응형 작업하기
  • 9. 푸터 영역
    • 9.1 푸터 구조 잡기
    • 9.2 푸터 디자인 설정
    • 9.3 반응형 작업하기
  • 10. 마무리
    • 10.1 스무스 효과주기
    • 10.2 링크 연결하기
    • 10.3 netlify에 배포하기

REACT SITE

  • 1. 셋팅하기
    • 1_1. React 설치하기
    • 1_2. React 폴더 정리하기
    • 1_3. 라이브러리 설치하기
    • 1_4. git 연동하기
  • 2. 라우팅 및 컴퍼넌트
    • 2_1. 라우팅 설정하기
    • 2_2. 컴퍼넌트 설정하기
    • 2_3. SCSS 설정하기
  • 3. 헤더 영역
    • 3_1. 헤더 구조잡기
    • 3_2. 헤더 디자인 설정
    • 3_3. 헤더 데이터 작업
    • 3_4. 헤더 토글 메뉴 작업하기
  • 4. 인트로 영역
    • 4_1. 인트로 구조잡기
    • 4_2. 인트로 디자인 설정
    • 4_3. 인트로 데이터 작업
  • 5. 스킬 영역
    • 5_1. 스킬 구조잡기
    • 5_2. 스킬 디자인 설정
    • 5_3. 스킬 데이터 작업
  • 6. 사이트 영역
    • 6_1. 사이트 구조잡기
    • 6_2. 사이트 디자인 설정
    • 6_3. 사이트 데이터 작업
  • 7. 포트폴리오 영역
    • 7_1. 포트폴리오 구조잡기
    • 7_2. 포트폴리오 디자인 설정
    • 7_3. 포트폴리오 데이터 작업
  • 8. 연락처 영역
    • 8_1. 연락처 구조잡기
    • 8_2. 연락처 디자인 설정
    • 8_3. 연락처 데이터 작업
  • 9. 푸터 영역
    • 9_1. 푸터 구조잡기
    • 9_2. 푸터 디자인 설정
    • 9_3. 푸터 데이터 작업
  • 10. 마무리
    • 10_1. 데이터 통합하기
    • 10_2. 스무스 효과 넣어주기
    • 10_3. 가로모드 구현하기
    • 10_4. netlify에 배포하기

VUE SITE

  • 1. 셋팅하기
    • 1_1. Vue 설치하기
    • 1_2. Vue 폴더 정리하기
    • 1_3. 라이브러리 설치하기
    • 1_4. git 연동하기
  • 2. 라우팅 및 컴퍼넌트
    • 2_1. 라우팅 설정하기
    • 2_2. 컴퍼넌트 설정하기
    • 2_3. SCSS 설정하기
  • 3. 헤더 영역
    • 3_1. 헤더 구조잡기
    • 3_2. 헤더 디자인 설정
    • 3_3. 헤더 데이터 작업
    • 3_4. 헤더 토글 메뉴 작업하기

3. 헤더 영역

3.1 헤더 구조잡기

Vue.js의 기본 컴포넌트 구조는 Single File Component(SFC)라고도 알려진 방식으로 작성됩니다. 이는 .vue 확장자를 가진 하나의 파일에 컴포넌트의 코드, 템플릿, 스타일을 모두 작성하는 방식입니다. 이렇게 하면 컴포넌트의 관련 코드가 한 곳에 모이므로 가독성과 유지보수가 향상됩니다.

  • <template> : HTML 템플릿을 정의하는 섹션입니다. Vue 컴포넌트는 템플릿을 사용하여 렌더링할 내용을 기술합니다. Vue 템플릿은 Vue 인스턴스의 데이터를 바인딩하고, 디렉티브를 사용하여 동적인 콘텐츠를 조작하며, 이벤트를 처리하는 등의 기능을 제공합니다.
  • <script> : JavaScript 코드를 정의하는 섹션입니다. Vue 컴포넌트의 동작과 데이터를 정의합니다. 일반적으로 data, computed, methods, watch 등의 속성을 사용하여 컴포넌트의 상태와 동작을 설정합니다. Vue 컴포넌트에서 외부 모듈을 가져오거나 다른 컴포넌트를 사용하기 위해 import 문이나 components 속성을 이용할 수 있습니다.
  • <style> : 컴포넌트에 대한 스타일을 정의하는 섹션입니다. 이곳에 작성된 스타일은 해당 컴포넌트의 요소에만 적용됩니다. Vue 컴포넌트에서는 다양한 방법으로 스타일을 작성할 수 있습니다. 기본적으로는 CSS를 사용할 수 있지만, scoped 속성을 추가하면 해당 컴포넌트의 스타일만 적용되도록 지정할 수 있습니다.
<template>
    HTML 템플릿을 정의하는 섹션
</template>
<script>
    JavaScript 코드를 정의하는 섹션
</script>
<style>
    스타일을 정의하는 섹션
</style>

vite에서 작업했던 소스를 그대로 가져와서 사용하겠습니다.

<template>
    <header id="header" role="banner">
        <div class="header__inner">
            <h1 class="header__logo">
                <a href="#">portfolio<em>vue.js</em></a>
            </h1>
            <nav class="header__nav" role="navigation" aria-label="메인 메뉴">
                <ul>
                    <li><a href="#intro">intro</a></li>
                    <li><a href="#skill">skill</a></li>
                    <li><a href="#site">site</a></li>
                    <li><a href="#port">portfolio</a></li>
                    <li><a href="#contact">contact</a></li>
                </ul>
            </nav>
            <div class="header__nav__mobile" id="headerToggle" aria-controls="primary-menu" aria-expanded="false" role="button" tabindex="0">
                <span></span>
            </div>
        </div>
    </header>
</template>

3.2 헤더 디자인 설정

SCSS 소스는 리액트에서 했던 것을 그대로 가져와서 사용하겠습니다.

@import "@/assets/scss/mixin";

#header {
    @include position-fixed;
    z-index: 10000;
}
.header__inner {
    @include flex-between;
    background-color: rgba(116, 99, 99, 0.1);
    backdrop-filter: blur(15px);
    padding: 1rem;

    .header__logo {
        font-size: 0.9rem;
        text-align: center;
        text-transform: uppercase;
        line-height: 1;

        em {
            font-size: 10px;
            display: block;
            color: var(--black200);
        }
    }
    
    .header__nav {

        @media (max-width: 800px){
            display: none;

            &.show {
                display: block;

                ul {
                    display: block;
                    position: absolute;
                    right: 0;
                    top: 68px;
                    background-color: rgba(116,99,99,0.1);
                    backdrop-filter: blur(15px);
                    z-index: 10000;
                    min-width: 150px;
                    padding: 20px 0;

                    li {
                        display: block;
                        text-align: right;

                        a {
                            display: inline-block;
                            padding: 10px;
                        }
                    }
                }
            }
            &.show + .header__nav__mobile span::before {
                width: 20px;
            }
            &.show + .header__nav__mobile span::after {
                width: 20px;
            }
        }
        
        li {
            display: inline;
    
            a {
                text-transform: uppercase;
                font-size: 14px;
                padding: 14px;
                position: relative;
    
                &::before {
                    content: '';
                    width: calc(100% - 28px);
                    height: 1px;
                    background-color: var(--black);
                    position: absolute;
                    left: 14px;
                    bottom: 10px;
                    transform: scaleX(0);
                    transition: all 0.3s;
                }
                &:hover::before {
                    transform: scaleX(1);
                }
            }
        }
    }
    
    .header__nav__mobile {
        display: none;
        width: 40px;
        height: 40px;
        cursor: pointer;

        @media (max-width: 800px){
            display: block;
        }

        span {
            display: block;
            width: 40px;
            height: 2px;
            background-color: var(--black);
            margin-top: 19px;
            position: relative;

            &::before {
                content: "";
                width: 40px;
                height: 2px;
                background-color: var(--black);
                position: absolute;
                right: 0;
                top: 6px;
                transition: width 0.3s;
            }
            &::after {
                content: "";
                width: 40px;
                height: 2px;
                background-color: var(--black);
                position: absolute;
                left: 0;
                bottom: 6px;
                transition: width 0.3s;
            }
        }
    }
} 

3.3 헤더 데이터 작업

뷰에서 데이터를 따로 작업하는 이유는 코드를 더 구조적이고 관리하기 쉽게 만들기 위해서입니다. 데이터를 따로 작업하여 관리함으로써 코드의 가독성과 유지보수성을 향상시킬 수 있습니다. 규모가 작은 사이트를 라면 불편할 수도 있는 작업이지만 규모가 커질 수록 관리하기가 쉬어집니다. constants 폴더에 index.js에 작업을 하겠습니다. 이 부분은 자바스크립트 모듈 기능이기 때문에 리액트랑 동일합니다.

export const headerNav = [
    {
        title: "intro",
        url: "#intro",
    },
    {
        title: "skill",
        url: "#skill",
    },
    {
        title: "site",
        url: "#site",
    },
    {
        title: "portfolio",
        url: "#port",
    },
    {
        title: "contact",
        url: "#contact",
    },
];

데이터 파일은 import { headerNav } from "@/constants/index"; 이렇게 연동을 하겠습니다. 리액트에서는 map()를 작업하여 반복적인 코드를 실행했지만, 뷰는 for문을 써서 HTML 코드에 바로 작업할 수 있습니다. 문법은 처음이지만 리액트를 했다면 어떤 원리인지 이해할 수 있습니다.

<script setup>
import { headerNav } from "@/constants/index";
</script>

<template>
    <header id="header" role="banner">
        <div class="header__inner">
            <h1 class="header__logo">
                <a href="#">portfolio<em>vue.js</em></a>
            </h1>
            <nav class="header__nav" role="navigation" aria-label="메인 메뉴">
                <ul>
                    <li v-for="(nav, key) in headerNav" :key="key">
                        <a :href="nav.url">{{ nav.title }}</a>
                    </li> 
                </ul> 
            </nav>
            <div class="header__nav__mobile" id="headerToggle" aria-controls="primary-menu" aria-expanded="false" role="button" tabindex="0">
                <span></span>
            </div>
        </div>
    </header>
</template>

3.4 헤더 토글 메뉴 작업하기

반응형이 되었을 때 메뉴를 클릭하면 서브 메뉴가 나오도록 작업하겠습니다. 리액트에서도 작업을 했지만, 뷰에서도 원리는 동일합니다.

<script setup>
import { headerNav } from "@/constants/index";
</script>

<template>
    <header id="header" role="banner">
        <div class="header__inner">
            <h1 class="header__logo">
                <a href="#">portfolio<em>vue.js</em></a>
            </h1>
            <nav class="header__nav" role="navigation" aria-label="메인 메뉴" :class="{ show: isNavVisible }">
                <ul>
                    <li v-for="(nav, key) in headerNav" :key="key">
                        <a :href="nav.url">{{ nav.title }}</a>
                    </li> 
                </ul> 
            </nav>
            <div
                class="header__nav__mobile"
                id="headerToggle"
                aria-controls="primary-menu"
                :aria-expanded="isNavVisible.toString()"
                @click="toggleMobileMenu"
            >
                <span></span>
            </div>
        </div>
    </header>
</template>

<script>
export default {
    data() {
        return {
            isNavVisible: false,
        };
    },
    methods: {
        toggleMobileMenu() {
            this.isNavVisible = !this.isNavVisible;
        },
    },
};
</script>
  • :class="{ show: isNavVisible }" : isNavVisible 데이터의 값에 따라 클래스 바인딩을 수행합니다. isNavVisible 값이 true일 때 show 클래스가 추가되어 메뉴가 표시되도록 합니다.
  • :aria-expanded="isNavVisible.toString()" : aria-expanded 속성을 isNavVisible의 불리언 값으로 바인딩하여 스크린 리더 사용자에게 메뉴의 펼침 여부를 알려줍니다.
  • @click="toggleMobileMenu" : 토글 메뉴 아이콘을 클릭했을 때 toggleMobileMenu 메서드를 호출합니다.

3. 마무리

익숙해질려고 하는데 다시 어렵나요? 아직 토글 메뉴에 대한 적용방법이 완전하게 익혀지지 않아서 그렇습니다. 하지만 괜찮습니다. 또 연습하고 만들어보면 됩니다. 그럼 오늘도 수고하셨습니다. 😎


예제 목록

댓글