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

수직 메뉴 - 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">: 서브메뉴 리스트- 초기 상태: 첫 번째 메뉴에
selected와active클래스가 적용되어 기본적으로 열려있음
클래스 역할:
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클래스 추가
토글 동작:
- 열려있으면: 초기화 후 아무것도 하지 않음 (닫힘)
- 닫혀있으면: 초기화 후
selected와active클래스 추가 (열림)
7. 기본 동작 방지
e.preventDefault();
return false;
설명:
e.preventDefault(): 링크의 기본 동작(페이지 이동) 방지return false: 추가적인 이벤트 전파 방지
핵심 동작 원리
1. 아코디언 메뉴 원리
[메뉴1] ← 클릭
└─ 서브메뉴1 (열림)
└─ 서브메뉴2
└─ 서브메뉴3
[메뉴2] ← 클릭 시
└─ 서브메뉴1 (닫힘)
└─ 서브메뉴2
└─ 서브메뉴3
동작 흐름:
- 메뉴 클릭
- 모든 메뉴 초기화 (닫기)
- 클릭한 메뉴가 닫혀있었다면 열기
2. CSS Transition 동작
/* 초기 상태 */
.sub {
height: 0; /* 높이 0 */
transition: height 0.3s ease; /* 전환 효과 */
}
/* 활성화 상태 */
.sub.active {
height: 108px; /* 높이 108px */
}
애니메이션 과정:
active클래스 추가height: 0→height: 108px로 변화transition에 의해 0.3초 동안 부드럽게 전환overflow: hidden으로 넘치는 부분 숨김
3. 클래스 토글 방식
장점:
- CSS로 애니메이션 처리 (성능 좋음)
- JavaScript는 클래스만 추가/제거
- 코드가 간단하고 이해하기 쉬움
단점:
- 정확한 높이를 알아야 함 (고정된 메뉴 개수 필요)
- 동적 높이에는
max-height사용 권장
코드 분석 포인트
HTML
- ✅ 시맨틱 태그 사용 (
<nav>) - ✅ 중첩 리스트 구조 (
<ul>안에<ul>) - ✅ 초기 상태 설정 (
selected,active클래스) - ✅ 접근성: 링크에 의미 있는 텍스트
CSS
- ✅ Height Transition:
height: 0→height: 108px - ✅ Overflow Hidden: 넘치는 내용 숨김
- ✅ Transition: 부드러운 애니메이션 효과
- ✅ 호버 효과: CSS만으로 처리
- ✅ 선택 상태:
selected클래스로 시각적 구분 - ✅ 자식 선택자:
>사용으로 정확한 선택
JavaScript
- ✅ 화살표 함수: ES6 문법 사용
- ✅ const: 상수 선언
- ✅ querySelectorAll: 다중 요소 선택
- ✅ forEach: 반복 처리
- ✅ classList API: 클래스 추가/제거
- ✅ 이벤트 위임: 각 링크에 개별 리스너 등록
- ✅ 인덱스 매칭:
index로 링크와 서브메뉴 매칭 - ✅ 조건부 로직: 토글 방식 구현
핵심 개념
- ✅ 단일 선택: 한 번에 하나의 메뉴만 열림
- ✅ 초기화 후 선택: 모든 메뉴 닫고 → 선택한 메뉴 열기
- ✅ 토글 동작: 열려있으면 닫고, 닫혀있으면 열기
- ✅ 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의 인덱스 활용
- 같은 순서로 배치된 요소 매칭
forEach의index파라미터 활용
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 학습 및 코드 분석 자료
'FrontEnd > Javascript' 카테고리의 다른 글
| Slick Slider 공부하기 (0) | 2026.02.26 |
|---|---|
| 이미지 슬라이더 무한 루프 - javascript (0) | 2026.02.21 |
| 아코디언 메뉴 (Width 방식) (0) | 2026.02.13 |
| 아코디언 메뉴 (Height + CSS Transition 방식) (0) | 2026.02.13 |
| JavaScript 익명 함수와 화살표 함수 (0) | 2026.02.13 |