본문 바로가기
  • 코딩, 허쌤이 떠먹여 줄게
FrontEnd/Javascript

세로메뉴 - 아코디언 메뉴

by 허쌤 2026. 2. 16.

v2_1.html - 수직 메뉴 아코디언 분석 문서

📋 목차

  1. 프로젝트 개요
  2. HTML 구조 분석
  3. CSS 스타일 분석
  4. JavaScript 기능 분석
  5. 핵심 동작 원리
  6. 코드 분석 포인트

 

 

수직 메뉴 - Active 클래스 방식

 

bhher.github.io

 

프로젝트 개요

파일명: v2_1.html
제목: 수직 메뉴 - Active 클래스 방식
주요 기능: 아코디언 메뉴 (Accordion Menu)
구현 방식: CSS height transition + JavaScript 클래스 토글
특징: 한 번에 하나의 메뉴만 열림 (단일 선택 방식)


HTML 구조 분석

1. 기본 구조

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>수직 메뉴 - Active 클래스 방식</title>
</head>

분석 포인트:

  • HTML5 문서 타입
  • 한국어 설정 (lang="ko")
  • 반응형 뷰포트 설정
  • UTF-8 인코딩

2. 네비게이션 구조

<nav class="mobile_nav">
    <ul>
        <li>
            <a href="#" class="selected">회사소개</a>
            <ul class="sub active">
                <li><a href="#">submenu1</a></li>
                <li><a href="#">submenu2</a></li>
                <li><a href="#">submenu3</a></li>
            </ul>
        </li>
        <!-- ... -->
    </ul>
</nav>

구조 설명:

  • <nav class="mobile_nav">: 메인 컨테이너
  • <ul>: 메인 메뉴 리스트
  • <li>: 각 메뉴 항목
  • <a>: 메인 메뉴 링크 (클릭 대상)
  • <ul class="sub">: 서브메뉴 리스트
  • 초기 상태: 첫 번째 메뉴에 selectedactive 클래스가 적용되어 기본적으로 열려있음

클래스 역할:

  • selected: 선택된 메인 메뉴 링크 (시각적 표시)
  • active: 열려있는 서브메뉴 (기능적 표시)

CSS 스타일 분석

1. 초기화 (Reset)

* {
    margin: 0;
    padding: 0;
}

ul {
    list-style: none;
}

a {
    text-decoration: none;
    color: #333;
}

분석:

  • 전체 요소의 마진/패딩 제거
  • 리스트 스타일 제거 (불릿 포인트 제거)
  • 링크 밑줄 제거, 기본 색상 설정

2. 메인 컨테이너

.mobile_nav{
    width: 200px;
    margin: 20px auto;
    text-align: center;
}

설명:

  • 고정 너비: 200px
  • 중앙 정렬: margin: 20px auto
  • 텍스트 중앙 정렬

3. 메인 메뉴 링크 스타일

.mobile_nav > ul > li > a{
    display: block;
    height: 36px;
    background-color: #390;
    line-height: 36px;
    border-top: 1px dotted #fff;
    color: #fff;
    cursor: pointer;
    transition: background-color 0.3s ease;
}

스타일 분석:

  • display: block: 전체 영역 클릭 가능
  • height: 36px: 고정 높이
  • line-height: 36px: 수직 중앙 정렬
  • background-color: #390: 기본 배경색 (녹색 계열)
  • border-top: 1px dotted #fff: 상단 점선 테두리
  • transition: background-color 0.3s ease: 배경색 변화 애니메이션

4. 호버 효과

.mobile_nav > ul > li > a:hover{
    background-color: #2a7a00;
}

설명:

  • 마우스 오버 시 배경색 변경
  • CSS만으로 처리 (JavaScript 불필요)
  • 더 어두운 녹색으로 변경

5. 선택된 메뉴 스타일

.mobile_nav > ul > li > a.selected{
    background-color: #1a5a00;
}

설명:

  • selected 클래스가 추가된 메뉴의 스타일
  • 가장 어두운 녹색으로 표시
  • 현재 열려있는 메뉴를 시각적으로 구분

6. 서브메뉴 초기 상태

.mobile_nav .sub {
    margin: 0;
    background-color: #cf9;
    height: 0;         /* 기본 높이 0 */
    overflow: hidden;  /* 넘치는 내용 숨김 */
    transition: height 0.3s ease; /* 높이 변화에 트랜지션 적용 */
}

핵심 포인트:

  • height: 0: 초기 상태는 높이가 0 (숨김)
  • overflow: hidden: 넘치는 내용 숨김 (중요!)
  • transition: height 0.3s ease: 높이 변화 시 0.3초 애니메이션
  • background-color: #cf9: 연한 녹색 배경

height를 사용하는가?

  • max-height 대신 height를 사용하여 정확한 높이 제어
  • 서브메뉴 항목이 3개이므로 정확한 높이 계산 가능 (36px × 3 = 108px)

7. 활성화된 서브메뉴

.mobile_nav .sub.active {
    height: 108px;     /* 열렸을 때 높이 (36px * 3개 메뉴) */
}

설명:

  • active 클래스가 추가되면 높이가 108px로 변경
  • transition에 의해 부드럽게 열림
  • 서브메뉴 항목 3개의 높이 합계

8. 서브메뉴 항목 스타일

.mobile_nav .sub li{
    background-color: #cf9;
}

.mobile_nav .sub li a{
    display: block;
    height: 36px;
    line-height: 36px;
    color: #222;
    transition: background-color 0.2s ease;
}

.mobile_nav .sub li a:hover{
    background-color: #9f9;
}

설명:

  • 서브메뉴 항목도 36px 높이
  • 호버 시 배경색 변경
  • 부드러운 전환 효과

JavaScript 기능 분석

1. DOM 준비 함수

document.addEventListener('DOMContentLoaded', () => {
    // 코드 내용
});

의미:

  • DOM이 완전히 로드된 후 실행
  • 화살표 함수 사용 (ES6 문법)
  • 이벤트 리스너 등록 전에 DOM 요소가 존재하는지 보장

2. 요소 선택

const mobileNavLinks = document.querySelectorAll(".mobile_nav > ul > li > a");
const subMenus = document.querySelectorAll(".mobile_nav .sub");

선택자 분석:

  • mobileNavLinks: 모든 메인 메뉴 링크 선택
  • subMenus: 모든 서브메뉴 선택
  • const: 상수 선언 (재할당 불가)
  • querySelectorAll: NodeList 반환 (배열과 유사)

3. 이벤트 리스너 등록

mobileNavLinks.forEach((link, index) => {
    link.addEventListener('click', (e) => {
        // 이벤트 처리 로직
    });
});

설명:

  • forEach: 각 링크에 대해 반복
  • (link, index): 현재 요소와 인덱스
  • addEventListener('click', ...): 클릭 이벤트 등록
  • index: 해당 링크와 매칭되는 서브메뉴를 찾기 위한 인덱스

4. 현재 상태 확인

const targetSubMenu = subMenus[index];
const isAlreadyOpen = targetSubMenu.classList.contains('active');

설명:

  • targetSubMenu: 클릭한 링크에 해당하는 서브메뉴
  • index를 사용하여 매칭 (같은 순서로 배치되어 있음)
  • isAlreadyOpen: 현재 서브메뉴가 이미 열려있는지 확인
  • classList.contains('active'): active 클래스 존재 여부 확인

5. 초기화 (모든 메뉴 닫기)

// 1. 모든 링크와 서브메뉴의 활성화 클래스 제거 (초기화)
mobileNavLinks.forEach(el => el.classList.remove('selected'));
subMenus.forEach(el => el.classList.remove('active'));

동작 원리:

  • 모든 메인 메뉴 링크에서 selected 클래스 제거
  • 모든 서브메뉴에서 active 클래스 제거
  • 화살표 함수 축약형 사용 (el => ...)

왜 모든 메뉴를 닫는가?

  • 한 번에 하나의 메뉴만 열리도록 하기 위해
  • 다른 메뉴가 열려있으면 먼저 닫음

6. 조건부 열기

// 2. 클릭한 요소 다음에 있는 서브메뉴가 닫혀있었다면 열기
if(!isAlreadyOpen){
    link.classList.add('selected');
    targetSubMenu.classList.add('active');
}

로직 설명:

  • !isAlreadyOpen: 닫혀있었다면
  • link.classList.add('selected'): 메인 메뉴 링크에 selected 클래스 추가
  • targetSubMenu.classList.add('active'): 서브메뉴에 active 클래스 추가

토글 동작:

  • 열려있으면: 초기화 후 아무것도 하지 않음 (닫힘)
  • 닫혀있으면: 초기화 후 selectedactive 클래스 추가 (열림)

7. 기본 동작 방지

e.preventDefault();
return false;

설명:

  • e.preventDefault(): 링크의 기본 동작(페이지 이동) 방지
  • return false: 추가적인 이벤트 전파 방지

핵심 동작 원리

1. 아코디언 메뉴 원리

[메뉴1] ← 클릭
  └─ 서브메뉴1 (열림)
  └─ 서브메뉴2
  └─ 서브메뉴3

[메뉴2] ← 클릭 시
  └─ 서브메뉴1 (닫힘)
  └─ 서브메뉴2
  └─ 서브메뉴3

동작 흐름:

  1. 메뉴 클릭
  2. 모든 메뉴 초기화 (닫기)
  3. 클릭한 메뉴가 닫혀있었다면 열기

2. CSS Transition 동작

/* 초기 상태 */
.sub {
    height: 0;                    /* 높이 0 */
    transition: height 0.3s ease;  /* 전환 효과 */
}

/* 활성화 상태 */
.sub.active {
    height: 108px;                 /* 높이 108px */
}

애니메이션 과정:

  1. active 클래스 추가
  2. height: 0height: 108px로 변화
  3. transition에 의해 0.3초 동안 부드럽게 전환
  4. overflow: hidden으로 넘치는 부분 숨김

3. 클래스 토글 방식

장점:

  • CSS로 애니메이션 처리 (성능 좋음)
  • JavaScript는 클래스만 추가/제거
  • 코드가 간단하고 이해하기 쉬움

단점:

  • 정확한 높이를 알아야 함 (고정된 메뉴 개수 필요)
  • 동적 높이에는 max-height 사용 권장

코드 분석 포인트

HTML

  1. ✅ 시맨틱 태그 사용 (<nav>)
  2. ✅ 중첩 리스트 구조 (<ul> 안에 <ul>)
  3. ✅ 초기 상태 설정 (selected, active 클래스)
  4. ✅ 접근성: 링크에 의미 있는 텍스트

CSS

  1. Height Transition: height: 0height: 108px
  2. Overflow Hidden: 넘치는 내용 숨김
  3. Transition: 부드러운 애니메이션 효과
  4. 호버 효과: CSS만으로 처리
  5. 선택 상태: selected 클래스로 시각적 구분
  6. 자식 선택자: > 사용으로 정확한 선택

JavaScript

  1. 화살표 함수: ES6 문법 사용
  2. const: 상수 선언
  3. querySelectorAll: 다중 요소 선택
  4. forEach: 반복 처리
  5. classList API: 클래스 추가/제거
  6. 이벤트 위임: 각 링크에 개별 리스너 등록
  7. 인덱스 매칭: index로 링크와 서브메뉴 매칭
  8. 조건부 로직: 토글 방식 구현

핵심 개념

  1. 단일 선택: 한 번에 하나의 메뉴만 열림
  2. 초기화 후 선택: 모든 메뉴 닫고 → 선택한 메뉴 열기
  3. 토글 동작: 열려있으면 닫고, 닫혀있으면 열기
  4. CSS Transition: JavaScript는 클래스만 조작

개선 가능한 부분

1. 동적 높이 지원

현재는 고정 높이(108px)를 사용하지만, 서브메뉴 개수가 변할 수 있다면:

.mobile_nav .sub.active {
    height: auto;  /* 자동 높이 */
}

문제점: height: auto는 transition이 작동하지 않음
해결책: max-height 사용 (예: max-height: 500px)

2. 접근성 개선

  • 키보드 네비게이션 지원
  • ARIA 속성 추가 (aria-expanded, aria-controls)
  • 포커스 스타일 추가

3. 성능 최적화

  • 이벤트 위임 사용 (하나의 리스너로 처리)
  • CSS 변수 사용 (색상 값 관리)

4. 코드 개선

// 이벤트 위임 방식
document.querySelector('.mobile_nav').addEventListener('click', (e) => {
    if(e.target.matches('.mobile_nav > ul > li > a')){
        // 처리 로직
    }
});

학습 포인트

1. CSS Transition 활용

  • height 속성에 transition 적용
  • overflow: hidden과 함께 사용
  • 정확한 높이 값 필요

2. 클래스 기반 상태 관리

  • CSS 클래스로 상태 표현
  • JavaScript는 클래스만 조작
  • 관심사의 분리 (Separation of Concerns)

3. 인덱스 기반 매칭

  • 배열/NodeList의 인덱스 활용
  • 같은 순서로 배치된 요소 매칭
  • forEachindex 파라미터 활용

4. 토글 로직

  • 초기화 → 조건부 실행
  • 상태 확인 후 동작 결정
  • 간단하고 명확한 로직

실행 예시

초기 상태

[회사소개] ← selected (어두운 녹색)
  ├─ submenu1
  ├─ submenu2  ← active (열림, 높이 108px)
  └─ submenu3

[제품소개]
[커뮤니티]
[다운로드]

"제품소개" 클릭 시

1. 모든 메뉴 초기화
   - [회사소개]에서 selected 제거
   - 서브메뉴에서 active 제거 (닫힘)

2. "제품소개" 열기
   - [제품소개]에 selected 추가
   - 서브메뉴에 active 추가 (열림)

결과

[회사소개]

[제품소개] ← selected (어두운 녹색)
  ├─ submenu1
  ├─ submenu2  ← active (열림, 높이 108px)
  └─ submenu3

[커뮤니티]
[다운로드]

관련 개념

1. 아코디언 메뉴 (Accordion Menu)

  • 한 번에 하나의 섹션만 열림
  • 다른 섹션은 자동으로 닫힘
  • 공간 효율적인 UI 패턴

2. CSS Transition

  • 속성 변화에 애니메이션 적용
  • transition: property duration timing-function
  • 하드웨어 가속 활용 가능

3. 클래스 토글 패턴

  • 상태를 클래스로 표현
  • JavaScript는 클래스만 조작
  • CSS가 시각적 변화 담당

4. 이벤트 처리

  • addEventListener: 이벤트 등록
  • preventDefault: 기본 동작 방지
  • classList: 클래스 조작 API

작성일: 2026-01-30
분석 대상: v2_1.html - 수직 메뉴 아코디언
용도: JavaScript 학습 및 코드 분석 자료