
1. 아코디언메뉴
아코디언 메뉴(Accordion Menu)는 상위 항목을 선택했을 때 하위 콘텐츠가 접히거나 펼쳐지는 사용자 인터페이스(UI) 구성 요소다. 아코디언 악기의 주름상자가 늘어나고 줄어들 듯이, 제한된 화면 공간 안에서 메뉴 영역을 효율적으로 확장하고 축소하는 데 사용된다. 각 상위 메뉴마다 포함된 하위 항목의 개수가 다를 때 고정된 높이를 부여하면 내용이 잘리거나 불필요한 여백이 발생하므로, 스크립트를 통해 하위 메뉴의 전체 높이를 가변적으로 계산하여 부드럽게 펼쳐지도록 제어해야 한다. 본 글에서는 제이쿼리(jQuery) 라이브러리와 순수 자바스크립트(Vanilla JavaScript)를 각각 활용하여 가변 높이 아코디언 메뉴를 구현하는 방법을 다룬다.
1.1. 제이쿼리
지시문
2차 메뉴(하위 메뉴)의 개수에 맞춰 높이를 스크립트에서 동적으로 계산하고 조작하는 아코디언 메뉴를 제이쿼리로 구현한다.
HTML
메뉴의 전체 계층 구조를 정의하는 마크업이다. 1차 대메뉴 역할을 하는 .gnb > li 요소 내부에 각각 하위 메뉴 목록인 ul.sub를 배치한다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<ul class="gnb">
<li>
<a href="#">회사소개</a>
<ul class="sub">
<li><a href="#">submenu1</a></li>
<li><a href="#">submenu2</a></li>
<li><a href="#">submenu3</a></li>
<li><a href="#">submenu4</a></li>
<li><a href="#">submenu5</a></li>
</ul>
</li>
<li>
<a href="#">제품소개</a>
<ul class="sub">
<li><a href="#">submenu1</a></li>
<li><a href="#">submenu2</a></li>
<li><a href="#">submenu3</a></li>
<li><a href="#">submenu4</a></li>
<li><a href="#">submenu5</a></li>
<li><a href="#">submenu6</a></li>
<li><a href="#">submenu7</a></li>
</ul>
</li>
<li>
<a href="#">커뮤니티</a>
<ul class="sub">
<li><a href="#">submenu1</a></li>
<li><a href="#">submenu2</a></li>
<li><a href="#">submenu3</a></li>
</ul>
</li>
<li>
<a href="#">다운로드</a>
<ul class="sub">
<li><a href="#">submenu1</a></li>
<li><a href="#">submenu2</a></li>
</ul>
</li>
</ul>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</body>
</html>
각 1차 메뉴 항목마다 포함된 하위 메뉴(.sub 내부의 li)의 개수가 5개, 7개, 3개, 2개로 서로 다르게 구성되어 있다. 하단에는 스크립트 실행을 위해 제이쿼리 라이브러리 CDN(콘텐츠 전송 네트워크) 링크를 선언한다.
CSS
메뉴의 레이아웃, 크기 단위, 초기 숨김 상태 및 애니메이션 전환 효과를 설정하는 스타일시트다.
* {
margin: 0;
padding: 0;
}
html {
font-size: 62.5%;
}
body {
font-size: 1.6rem;
}
a {
text-decoration: none;
}
ul,
li {
list-style: none;
}
.gnb {
width: 20rem;
margin: 2rem auto 0;
text-align: center;
text-indent: 1rem;
}
.gnb > li > a {
display: block;
background: #390;
cursor: pointer;
line-height: 3rem;
border-top: 0.1rem dotted #fff;
color: #fff;
}
.gnb .sub li {
line-height: 3rem;
background: #cf9;
padding: 1rem;
}
.gnb .sub li a {
display: block;
width: 100%;
height: 100%;
color: #222;
}
.gnb .sub li:hover {
background: #cf0;
}
.sub {
height: 0;
transition: height 0.5s;
overflow: hidden;
}
주요 스타일 속성의 역할과 주의점은 다음과 같다.
html { font-size: 62.5%; }: 기본 브라우저 폰트 크기(16px)를 기준으로 62.5%인 10px를 1rem의 기준 크기로 맞추어 계산을 단순화한다..sub { height: 0; overflow: hidden; }: 하위 메뉴의 높이를 처음에 0으로 두고 영역 밖으로 벗어나는 내용을 숨긴다.overflow: hidden이 빠지면 높이가 0이어도 내부 텍스트가 화면에 그대로 노출되므로 반드시 지정해야 한다.transition: height 0.5s;: 트랜지션(Transition, 속성 값의 변화를 부드럽게 이어주는 기능)을 적용하여 높이 값이 변경될 때 0.5초 동안 점진적으로 늘어나거나 줄어들도록 애니메이션 효과를 부여한다.
JQ
사용자가 1차 메뉴를 클릭했을 때 해당 하위 메뉴의 항목 개수를 파악하여 높이를 계산하고 펼치는 제이쿼리 스크립트다.
$(() => {
$('.gnb>li').click(function () {
let tg = $(this);
let subH = $('.sub>li').innerHeight();
let subLen = tg.find('li>a').length;
let subTH = subH * subLen;
$('.sub').removeClass('on').outerHeight(0);
tg.find('.sub').addClass('on').outerHeight(subTH);
});
});
동작 원리와 핵심 코드는 다음과 같다.
let tg = $(this);: 클릭 이벤트가 발생한 1차 메뉴li요소를 참조한다.let subH = $('.sub>li').innerHeight();: 하위 메뉴li항목 하나의 높이(패딩 포함)를 구한다.let subLen = tg.find('li>a').length;: 현재 선택된 메뉴 내부에 존재하는 하위 링크(a) 요소의 총 개수를 구한다.let subTH = subH * subLen;: 항목 1개의 높이와 개수를 곱하여 서브메뉴 전체에 필요한 총 높이(Total Height)를 계산한다.$('.sub').removeClass('on').outerHeight(0);: 기존에 펼쳐져 있던 모든 하위 메뉴를 닫기 위해on클래스를 제거하고 높이를 0으로 되돌린다.tg.find('.sub').addClass('on').outerHeight(subTH);: 현재 선택한 하위 메뉴에on클래스를 부여하고 계산된 전체 높이를 할당하여 부드럽게 펼쳐지도록 만든다.
1.2. 자바스크립트
지시문
동일한 가변 높이 아코디언 메뉴 구조를 외부 라이브러리 없이 브라우저 기본 내장 API인 순수 자바스크립트(Vanilla JS)로 구현한다.
HTML
제이쿼리 구현과 동일한 마크업 구조를 사용한다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<ul class="gnb">
<li>
<a href="#">회사소개</a>
<ul class="sub">
<li><a href="#">submenu1</a></li>
<li><a href="#">submenu2</a></li>
<li><a href="#">submenu3</a></li>
<li><a href="#">submenu4</a></li>
<li><a href="#">submenu5</a></li>
</ul>
</li>
<li>
<a href="#">제품소개</a>
<ul class="sub">
<li><a href="#">submenu1</a></li>
<li><a href="#">submenu2</a></li>
<li><a href="#">submenu3</a></li>
<li><a href="#">submenu4</a></li>
<li><a href="#">submenu5</a></li>
<li><a href="#">submenu6</a></li>
<li><a href="#">submenu7</a></li>
</ul>
</li>
<li>
<a href="#">커뮤니티</a>
<ul class="sub">
<li><a href="#">submenu1</a></li>
<li><a href="#">submenu2</a></li>
<li><a href="#">submenu3</a></li>
</ul>
</li>
<li>
<a href="#">다운로드</a>
<ul class="sub">
<li><a href="#">submenu1</a></li>
<li><a href="#">submenu2</a></li>
</ul>
</li>
</ul>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</body>
</html>
CSS
기본 레이아웃 및 .sub 요소의 overflow: hidden, transition 설정이 동일하게 유지된다.
* {
margin: 0;
padding: 0;
}
html {
font-size: 62.5%;
}
body {
font-size: 1.6rem;
}
a {
text-decoration: none;
}
ul,
li {
list-style: none;
}
.gnb {
width: 20rem;
margin: 2rem auto 0;
text-align: center;
text-indent: 1rem;
}
.gnb > li > a {
display: block;
background: #390;
cursor: pointer;
line-height: 3rem;
border-top: 0.1rem dotted #fff;
color: #fff;
}
.gnb .sub li {
line-height: 3rem;
background: #cf9;
padding: 1rem;
}
.gnb .sub li a {
display: block;
width: 100%;
height: 100%;
color: #222;
}
.gnb .sub li:hover {
background: #cf0;
}
.sub {
height: 0;
transition: height 0.5s;
overflow: hidden;
}
JQ
자바스크립트 구현과의 비교를 위한 제이쿼리 참조 코드다.
$(() => {
$('.gnb>li').click(function () {
let tg = $(this);
let subH = $('.sub>li').innerHeight();
let subLen = tg.find('li>a').length;
let subTH = subH * subLen;
$('.sub').removeClass('on').outerHeight(0);
tg.find('.sub').addClass('on').outerHeight(subTH);
});
});
JS
표준 돔(DOM, 문서 객체 모델) 메서드를 활용하여 이벤트를 연결하고 높이를 직접 제어하는 자바스크립트 코드다.
const depth1 = document.querySelectorAll('.gnb>li');
const sub = document.querySelectorAll('.sub');
depth1.forEach((el, idx) => {
el.addEventListener('click', function () {
let tg = this;
let subH = document.querySelector('.sub>li').clientHeight;
let subLen = tg.children[1].childElementCount;
let subHT = subH * subLen;
console.log(subHT);
tg.querySelector('.sub').classList.remove('on');
subOff();
tg.querySelector('.sub').style.height = subHT + 'px';
});
});
function subOff() {
sub.forEach((el) => {
el.classList.remove('on');
el.style.height = 0;
});
}
자바스크립트 코드의 핵심 처리 과정과 주의할 점은 다음과 같다.
document.querySelectorAll('.gnb>li'): 1차 메뉴 요소 전체를 노드리스트(NodeList, DOM 요소 목록) 형태로 수집한다.depth1.forEach(...): 각 1차 메뉴 요소에addEventListener('click', ...)를 통해 클릭 이벤트를 등록한다.let subH = document.querySelector('.sub>li').clientHeight;:clientHeight속성을 통해 패딩을 포함한 하위 항목 1개의 픽셀 높이를 측정한다.let subLen = tg.children[1].childElementCount;:tg(현재 클릭된li)의 두 번째 자식 요소인ul.sub가 보유한 자식 요소(li)의 개수를 구한다. 마크업 구조에서 인덱스가 변경되면 다른 요소를 참조할 수 있으므로 자식 요소의 위치 관계에 유의해야 한다.subOff()함수: 모든.sub요소를 순회하며on클래스를 제거하고style.height를 0으로 초기화하여 열려 있던 다른 메뉴를 닫는다.tg.querySelector('.sub').style.height = subHT + 'px';: 계산된 높이 값에 반드시'px'단위를 문자열로 결합하여 인라인 스타일에 대입해야 브라우저에서 올바른 높이로 렌더링되고 CSS 트랜지션이 동작한다.