frontend/javascript

🟨 2-2. 실전 이벤트 패턴 — 모달, 드롭다운, 아코디언, 탭 메뉴 한 번에 완성하기

mirabo01 2025. 11. 7. 08:47

1. 이번 편에서 배울 내용

이 글에서는 이벤트 시스템을 실무 UI에 적용하는 법을 다룬다.
하나의 공통 키워드는 바로 “클릭(click)”과 상태 전환(toggle) 이다.

배울 구성은 다음 네 가지다.

1️⃣ 모달 (Modal) — 화면 중앙에 띄워지는 팝업
2️⃣ 드롭다운 (Dropdown) — 선택 옵션 표시/숨기기
3️⃣ 아코디언 (Accordion) — 클릭 시 내용 열기/닫기
4️⃣ 탭 메뉴 (Tab Menu) — 탭 전환으로 컨텐츠 전환


2. 준비 코드 (공통 HTML & CSS)

우선 모든 예제에 공통으로 쓸 최소한의 스타일부터 추가하자.

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>이벤트 패턴 예제</title>
<style>
body {
  font-family: sans-serif;
  margin: 40px;
}

section {
  margin-bottom: 60px;
  border: 1px solid #ddd;
  padding: 20px;
  border-radius: 10px;
}

h2 {
  margin-bottom: 16px;
}

/* 공통 버튼 스타일 */
button {
  padding: 6px 12px;
  border: none;
  background: #4a90e2;
  color: #fff;
  border-radius: 5px;
  cursor: pointer;
}
button:hover {
  background: #3b78c5;
}
</style>
</head>
<body>

<script type="module" src="main.js"></script>
</body>
</html>

이제 <body> 안에 각 섹션을 하나씩 추가하면서 실습해보자.


3. 모달 (Modal) — 클릭으로 열고 닫기

HTML

<section>
  <h2>1️⃣ 모달 (Modal)</h2>
  <button id="openModal">모달 열기</button>

  <div id="modal" class="modal">
    <div class="modal-content">
      <p>이건 모달입니다 👋</p>
      <button id="closeModal">닫기</button>
    </div>
  </div>
</section>

CSS

.modal {
  display: none;
  position: fixed;
  top: 0; left: 0;
  width: 100%; height: 100%;
  background: rgba(0,0,0,0.5);
  justify-content: center; align-items: center;
}
.modal.show { display: flex; }
.modal-content {
  background: #fff;
  padding: 30px;
  border-radius: 10px;
  text-align: center;
}

JS

const modal = document.getElementById("modal");
const openBtn = document.getElementById("openModal");
const closeBtn = document.getElementById("closeModal");

openBtn.addEventListener("click", () => modal.classList.add("show"));
closeBtn.addEventListener("click", () => modal.classList.remove("show"));

// 모달 바깥 영역 클릭 시 닫기
modal.addEventListener("click", (e) => {
  if (e.target === modal) modal.classList.remove("show");
});

핵심 포인트

  • classList.toggle() 대신 add/remove로 명확하게 제어
  • 오버레이 영역(modal 자체)을 클릭했을 때 닫히도록 조건 분기
  • 실제 서비스에서도 거의 동일하게 쓰인다

4. 드롭다운 (Dropdown) — 클릭으로 옵션 토글

HTML

<section>
  <h2>2️⃣ 드롭다운 (Dropdown)</h2>
  <div class="dropdown">
    <button id="dropdownBtn">메뉴 ▼</button>
    <ul id="dropdownMenu" class="dropdown-menu">
      <li>HTML</li>
      <li>CSS</li>
      <li>JavaScript</li>
    </ul>
  </div>
</section>

CSS

.dropdown {
  position: relative;
  display: inline-block;
}

.dropdown-menu {
  display: none;
  position: absolute;
  top: 40px;
  left: 0;
  background: white;
  border: 1px solid #ccc;
  width: 150px;
  border-radius: 6px;
}

.dropdown-menu li {
  padding: 10px;
  cursor: pointer;
}
.dropdown-menu li:hover {
  background: #eee;
}
.dropdown-menu.show {
  display: block;
}

JS

const dropdownBtn = document.getElementById("dropdownBtn");
const dropdownMenu = document.getElementById("dropdownMenu");

dropdownBtn.addEventListener("click", (e) => {
  dropdownMenu.classList.toggle("show");
});

// 다른 곳 클릭 시 자동 닫기 (이벤트 위임 + 버블링 활용)
document.addEventListener("click", (e) => {
  if (!e.target.closest(".dropdown")) {
    dropdownMenu.classList.remove("show");
  }
});

핵심 포인트

  • closest() 메서드로 부모 요소 판별
  • 외부 클릭 시 닫히게 만들어 UX 향상
  • 이벤트 위임 + 버블링 제어의 좋은 실전 예시

5. 아코디언 (Accordion) — 클릭 시 내용 열고 닫기

HTML

<section>
  <h2>3️⃣ 아코디언 (Accordion)</h2>
  <div class="accordion">
    <div class="item">
      <div class="title">HTML이란?</div>
      <div class="content">HTML은 웹의 구조를 정의하는 언어입니다.</div>
    </div>
    <div class="item">
      <div class="title">CSS란?</div>
      <div class="content">CSS는 스타일을 담당하는 언어입니다.</div>
    </div>
    <div class="item">
      <div class="title">JavaScript란?</div>
      <div class="content">JS는 동작과 상호작용을 구현하는 언어입니다.</div>
    </div>
  </div>
</section>

CSS

.accordion .item {
  border-bottom: 1px solid #ddd;
  padding: 10px 0;
}
.accordion .title {
  cursor: pointer;
  font-weight: bold;
}
.accordion .content {
  display: none;
  margin-top: 6px;
  color: #555;
}
.accordion .content.show {
  display: block;
}

JS

const accordion = document.querySelector(".accordion");
accordion.addEventListener("click", (e) => {
  if (!e.target.classList.contains("title")) return;

  const content = e.target.nextElementSibling;
  content.classList.toggle("show");
});

핵심 포인트

  • “이벤트 위임(Event Delegation)”의 전형적인 사용 예
  • nextElementSibling 으로 바로 다음 내용 블록 접근
  • 각 아이템을 독립적으로 열고 닫을 수 있음

6. 탭 메뉴 (Tab Menu) — 컨텐츠 전환

HTML

<section>
  <h2>4️⃣ 탭 메뉴 (Tab Menu)</h2>
  <div class="tabs">
    <div class="tab-buttons">
      <button data-tab="html">HTML</button>
      <button data-tab="css">CSS</button>
      <button data-tab="js">JavaScript</button>
    </div>
    <div class="tab-contents">
      <div class="tab-content" id="html">HTML은 웹의 구조를 구성합니다.</div>
      <div class="tab-content" id="css">CSS는 스타일을 담당합니다.</div>
      <div class="tab-content" id="js">JavaScript는 동작을 담당합니다.</div>
    </div>
  </div>
</section>

CSS

.tab-buttons {
  margin-bottom: 10px;
}
.tab-buttons button {
  margin-right: 8px;
  background: #ddd;
  color: #000;
}
.tab-buttons button.active {
  background: #4a90e2;
  color: #fff;
}
.tab-content {
  display: none;
  border: 1px solid #ccc;
  padding: 10px;
  border-radius: 8px;
}
.tab-content.active {
  display: block;
}

JS

const tabButtons = document.querySelectorAll(".tab-buttons button");
const tabContents = document.querySelectorAll(".tab-content");

tabButtons.forEach((btn) => {
  btn.addEventListener("click", () => {
    const target = btn.dataset.tab;

    // 모든 탭 비활성화
    tabButtons.forEach((b) => b.classList.remove("active"));
    tabContents.forEach((c) => c.classList.remove("active"));

    // 선택된 탭만 활성화
    btn.classList.add("active");
    document.getElementById(target).classList.add("active");
  });
});

핵심 포인트

  • dataset 속성(data-tab)으로 탭 대상 연결
  • 상태 변경 시 class 토글로 컨텐츠 제어
  • 실제 서비스의 탭, 설정, 메뉴 구조 전부 같은 원리

7. 이 4가지 패턴의 공통점

패턴 핵심 이벤트 상태 관리 방식 주요 메서드

모달 click class add/remove classList
드롭다운 click + document toggle + 외부 클릭 감지 closest()
아코디언 click (위임) 개별 열림/닫힘 nextElementSibling
click 단일 활성 상태 dataset, forEach

이 4개만 완벽히 이해하면,
React나 Vue로 넘어갔을 때 “UI 상태 전환의 본질”을 완전히 이해할 수 있다.
프레임워크는 결국 이런 로직을 더 깔끔하게 래핑한 도구일 뿐이니까.


8. 실무에서 확장 아이디어

  • 모달에 ESC 키 닫기 기능 추가 → keydown 이벤트 활용
  • 드롭다운에 키보드 네비게이션 추가
  • 아코디언을 하나만 열리게 변경 → “활성 상태 관리 변수” 추가
  • 탭에 URL hash(#tab-js) 동기화 → 새로고침 후에도 유지

이런 식으로 발전시켜 나가면
하나의 UI 패턴을 수십 가지 방식으로 확장할 수 있다.


9. 마무리

이제 당신은 단순히 이벤트를 “배웠다”가 아니라,
이벤트로 UI를 설계할 수 있는 수준에 도달했다.

이 4개의 예제만으로도

  • 모달 창
  • 네비게이션 메뉴
  • FAQ 섹션
  • 설정 페이지 탭
    을 직접 구현할 수 있다.

"이벤트는 단순한 함수 호출이 아니라, 사용자 경험을 설계하는 도구다."