
강화된 드롭다운
데이터 필터링 기능은 사용자 경험을 크게 향상시키는 중요한 UI 요소다. 특히 여러 옵션을 선택하고 그에 따른 결과를 실시간으로 확인해야 하는 경우 필수적이다.
웹사이트나 애플리케이션에서 많은 양의 데이터를 효과적으로 관리하려면 사용자가 원하는 정보만 필터링할 수 있는 기능이 필요하다.
이런 필터링 기능이 있는 드롭다운 메뉴는 사용자 친화적인 인터페이스를 제공하여 데이터 탐색을 훨씬 쉽게 만든다.
예시를 먼저 확인해보자.
이제부터 HTML, CSS, JavaScript를 사용하여 필터링 기능이 있는 드롭다운 메뉴를 만드는 방법을 단계별로 알아보자.
기본 구조
필터링 기능이 있는 드롭다운 메뉴를 만들기 위해 먼저 HTML 구조를 설계해야 한다.
기본적으로 드롭다운 메뉴는 다음과 같은 주요 요소로 구성된다:
- 드롭다운 헤더
-
사용자가 클릭하여 메뉴를 열고 닫을 수 있는 영역
-
- 선택된 항목 표시 영역
-
사용자가 선택한 항목들이 태그 형태로 표시되는 부분
-
- 드롭다운 메뉴
-
선택 가능한 옵션들이 나열된 영역
-
- 검색 입력란
-
옵션을 검색할 수 있는 입력 필드
-
- 옵션 목록
-
체크박스와 함께 선택 가능한 항목들이 나열된 부분
-
아래는 드롭다운 메뉴의 기본 HTML 구조다:
<div class="container">
<label class="dropdown-label">지역 선택</label>
<div class="dropdown-container">
<div class="dropdown-header" id="dropdown-toggle">
<div id="tags-container">
<div class="dropdown-npcholder" id="dropdown-npcholder">지역 선택...</div>
</div>
<div class="arrow-icon">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 9L12 15L18 9" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</div>
<div class="dropdown-menu" id="dropdown-menu">
<div class="search-container">
<div class="search-input">
<input type="text" npcholder="검색..." id="search-input">
<div class="search-icon">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z" stroke="#aaa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</div>
</div>
<div class="option-list" id="option-list">
<div class="option-item">
<input type="checkbox" class="checkbox location-checkbox" id="option-tirconaill" value="tirconaill" data-name="티르코네일" checked>
<label for="option-tirconaill">티르코네일</label>
</div>
<!-- 추가 옵션 항목들 -->
</div>
</div>
</div>
</div>
이러한 기본 구조를 바탕으로 필터링된 결과를 표시할 영역도 함께 구현해야 한다:
<div class="filtered-checkboxes-container" id="filtered-checkboxes-container">
<h3 class="filtered-checkboxes-title">선택된 지역의 NPC</h3>
<div class="filtered-checkboxes-list" id="filtered-checkboxes-list">
<div class="no-selection-message" id="no-selection-message">
위에서 지역을 선택하면 그 지역의 NPC가 표시됩니다.
</div>
<div class="location-group" id="location-tirconaill">
<div class="filtered-checkbox-header">티르코네일</div>
<div class="filtered-checkbox-item">
<input type="checkbox" id="npc-tirconaill-1" class="checkbox npc-checkbox" value="던컨">
<label for="npc-tirconaill-1">던컨</label>
</div>
<!-- 추가 체크박스 항목들 -->
</div>
<!-- 추가 지역 그룹들 -->
</div>
</div>
CSS
드롭다운 메뉴의 시각적인 부분을 구현하기 위해 CSS를 사용한다.
전체적인 레이아웃부터 세부적인 애니메이션까지 CSS를 통해 사용자 경험을 향상시킬 수 있다.
CSS 스타일을 설정해보자.
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Arial', sans-serif;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f5f5f5;
padding: 20px;
flex-direction: column;
}
.container {
width: 100%;
max-width: 400px;
margin-bottom: 20px;
}
.dropdown-label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.dropdown-container {
position: relative;
width: 100%;
}
.dropdown-header {
min-height: 40px;
width: 100%;
padding: 4px 40px 4px 8px;
background: white;
border: 1px solid #ccc;
border-radius: 5px;
cursor: pointer;
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 5px;
}
.selected-tag {
display: inline-flex;
align-items: center;
background-color: #e0e7ff;
color: #4f46e5;
border-radius: 4px;
padding: 4px 8px;
margin: 2px;
font-size: 14px;
}
.tag-close {
margin-left: 6px;
cursor: pointer;
color: #4f46e5;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
.dropdown-menu {
position: absolute;
top: calc(100% + 5px);
left: 0;
width: 100%;
background: white;
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
max-height: 300px;
overflow-y: auto;
z-index: 1000;
display: none;
}
.dropdown-menu.open {
display: block;
}
검색 기능과 옵션 목록에 대한 추가적인 스타일을 설정해보자.
.search-container {
padding: 10px;
border-bottom: 1px solid #eee;
position: sticky;
top: 0;
background: white;
z-index: 1;
}
.search-input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 5px;
display: flex;
align-items: center;
}
.search-input input {
flex: 1;
border: none;
outline: none;
font-size: 14px;
}
.option-list {
padding: 5px 0;
}
.option-item {
padding: 8px 16px;
display: flex;
align-items: center;
cursor: pointer;
}
.option-item:hover {
background-color: #f5f5f5;
}
.dropdown-menu::-webkit-scrollbar {
width: 8px;
}
.dropdown-menu::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 0 5px 5px 0;
}
.dropdown-menu::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
필터링된 결과를 표시하는 영역에 대한 스타일링도 추가하자.
.filtered-checkboxes-container {
width: 100%;
max-width: 600px;
background-color: white;
border: 1px solid #ccc;
border-radius: 5px;
padding: 15px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.filtered-checkboxes-list {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.location-group {
display: none;
}
.location-group.visible {
display: block;
}
.filtered-checkbox-header {
grid-column: 1 / span 2;
font-weight: bold;
margin-top: 15px;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #eee;
color: #4f46e5;
}
@media (max-width: 480px) {
.filtered-checkboxes-list {
grid-template-columns: 1fr;
}
.filtered-checkbox-header {
grid-column: 1;
}
}
JavaScript
이제 드롭다운 메뉴의 동작을 구현하기 위한 JavaScript 코드를 작성해보자.
주요 기능으로는 드롭다운 메뉴 열기/닫기, 옵션 선택, 태그 생성 및 삭제, 검색 기능 등이 있다.
먼저 기본적인 이벤트 리스너 설정부터 시작하자.
document.addEventListener('DOMContentLoaded', function() {
const dropdownToggle = document.getElementById('dropdown-toggle');
const dropdownMenu = document.getElementById('dropdown-menu');
const arrowIcon = document.querySelector('.arrow-icon');
const searchInput = document.getElementById('search-input');
const optionItems = document.querySelectorAll('.option-item');
const locationCheckboxes = document.querySelectorAll('.location-checkbox');
const tagsContainer = document.getElementById('tags-container');
const dropdownnpcholder = document.getElementById('dropdown-npcholder');
const locationGroups = document.querySelectorAll('.location-group');
const noSelectionMessage = document.getElementById('no-selection-message');
dropdownToggle.addEventListener('click', function(event) {
if (event.target.classList.contains('tag-close') ||
event.target.parentElement.classList.contains('tag-close')) {
return;
}
dropdownMenu.classList.toggle('open');
arrowIcon.classList.toggle('open');
});
document.addEventListener('click', function(event) {
if (!dropdownToggle.contains(event.target) && !dropdownMenu.contains(event.target)) {
dropdownMenu.classList.remove('open');
arrowIcon.classList.remove('open');
}
});
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
optionItems.forEach(function(item) {
const text = item.textContent.toLowerCase();
if (text.includes(searchTerm)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
});
locationCheckboxes.forEach(function(checkbox) {
checkbox.addEventListener('change', function() {
updateTags();
updateLocationVisibility();
});
});
선택된 옵션에 대한 태그를 생성하고 관리하는 함수를 구현해보자.
function updateTags() {
const existingTags = document.querySelectorAll('.selected-tag');
existingTags.forEach(tag => tag.remove());
let hasSelectedOptions = false;
locationCheckboxes.forEach(function(checkbox) {
if (checkbox.checked) {
hasSelectedOptions = true;
const locationName = checkbox.getAttribute('data-name');
const tag = createTag(locationName, checkbox.id);
tagsContainer.insertBefore(tag, dropdownnpcholder);
}
});
if (hasSelectedOptions) {
dropdownnpcholder.style.display = 'none';
} else {
dropdownnpcholder.style.display = 'block';
}
}
function createTag(value, checkboxId) {
const tag = document.createElement('div');
tag.className = 'selected-tag';
tag.innerHTML = `
${value}
<span class="tag-close" data-checkbox-id="${checkboxId}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
`;
const closeBtn = tag.querySelector('.tag-close');
closeBtn.addEventListener('click', function(event) {
event.stopPropagation();
const checkboxId = this.getAttribute('data-checkbox-id');
const checkbox = document.getElementById(checkboxId);
checkbox.checked = false;
updateTags();
updateLocationVisibility();
});
return tag;
}
필터링
선택한 옵션에 따라 콘텐츠를 필터링하는 기능을 구현해보자.
이 예제에서는 선택한 지역에 따라 해당 지역의 NPC 목록을 표시하는 기능을 구현한다.
function updateLocationVisibility() {
let anyLocationVisible = false;
locationCheckboxes.forEach(function(checkbox) {
const locationValue = checkbox.value;
const locationGroup = document.getElementById(`location-${locationValue}`);
if (checkbox.checked) {
locationGroup.classList.add('visible');
anyLocationVisible = true;
} else {
locationGroup.classList.remove('visible');
}
});
if (anyLocationVisible) {
noSelectionMessage.classList.remove('visible');
} else {
noSelectionMessage.classList.add('visible');
}
}
updateTags();
updateLocationVisibility();
이 함수는 체크박스의 상태에 따라 해당 지역의 NPC 목록을 표시하거나 숨기는 역할을 한다.
선택된 지역이 없을 경우 안내 메시지를 표시하여 사용자에게 다음 단계를 안내한다. 이는 사용자 경험을 향상시키는 중요한 요소로, 사용자가 어떤 행동을 취해야 하는지 명확하게 알려준다.
이러한 안내 메시지는 사용자가 인터페이스를 처음 접했을 때 혼란을 줄이고, 필요한 상호작용을 유도하는 데 도움이 된다. 특히 필터링 기능에서는 선택 항목이 없을 때 결과가 표시되지 않는 이유를 명확히 전달하는 것이 중요하다.
검색
드롭다운 메뉴 내에서 옵션을 쉽게 찾을 수 있도록 검색 기능을 구현하는 것이 중요하다.
특히 옵션이 많을 경우 사용자가 원하는 항목을 빠르게 찾을 수 있도록 도와준다.
앞서 작성한 JavaScript 코드에서 검색 기능 부분을 좀 더 자세히 알아보자.
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
optionItems.forEach(function(item) {
const text = item.textContent.toLowerCase();
if (text.includes(searchTerm)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
});
이 코드는 사용자가 검색창에 텍스트를 입력할 때마다 실행되며, 입력된 텍스트가 포함된 옵션만 표시하고 나머지는 숨긴다.
검색 기능을 더 개선하기 위해 다음과 같은 추가 기능을 구현할 수도 있다:
- 검색어 하이라이트
-
검색된 텍스트를 시각적으로 강조하여 사용자가 쉽게 식별할 수 있게 함
-
- 자동 완성
-
사용자가 입력하는 동안 가능한 옵션을 제안하여 검색 속도를 높임
-
- 검색 결과 없음 메시지
-
검색 결과가 없을 때 사용자에게 알려주는 메시지 표시
-
다음은 검색어 하이라이트 기능을 추가한 개선된 코드 예시다:
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
let hasResults = false;
optionItems.forEach(function(item) {
const label = item.querySelector('label');
const originalText = label.getAttribute('data-original-text') || label.textContent;
if (!label.getAttribute('data-original-text')) {
label.setAttribute('data-original-text', originalText);
}
const text = originalText.toLowerCase();
if (text.includes(searchTerm) && searchTerm !== '') {
const regex = new RegExp(`(${searchTerm})`, 'gi');
label.innerHTML = originalText.replace(regex, '$1');
item.style.display = 'flex';
hasResults = true;
} else if (searchTerm === '') {
label.textContent = originalText;
item.style.display = 'flex';
hasResults = true;
} else {
item.style.display = 'none';
}
});
const noResultsMsg = document.getElementById('no-results-message') ||
createNoResultsMessage();
if (!hasResults) {
noResultsMsg.style.display = 'block';
} else {
noResultsMsg.style.display = 'none';
}
});
function createNoResultsMessage() {
const msg = document.createElement('div');
msg.id = 'no-results-message';
msg.className = 'no-results';
msg.textContent = '검색 결과가 없습니다';
msg.style.padding = '10px';
msg.style.textAlign = 'center';
msg.style.color = '#666';
const optionList = document.getElementById('option-list');
optionList.appendChild(msg);
return msg;
}
반응형
다양한 화면 크기에서도 드롭다운 메뉴가 잘 작동하도록 반응형 디자인을 적용하는 것이 중요하다.
CSS 미디어 쿼리를 사용하여 화면 크기에 따라 레이아웃을 조정할 수 있다.
앞서 작성한 CSS 코드에서 반응형으로 디자인을 변경해보자.
@media (max-width: 480px) {
.filtered-checkboxes-list {
grid-template-columns: 1fr;
}
.filtered-checkbox-header {
grid-column: 1;
}
.container {
max-width: 100%;
}
.dropdown-header {
padding: 6px 40px 6px 6px;
}
.selected-tag {
font-size: 12px;
padding: 3px 6px;
}
}
모바일 기기에서의 사용성을 더욱 향상시키기 위해 다음과 같은 추가적인 반응형 조정을 고려할 수 있다.
- 터치 친화적인 요소 크기
-
모바일에서는 체크박스와 버튼 크기를 더 크게 설정하여 터치하기 쉽게 함
-
- 스크롤 동작 최적화
-
모바일 기기에서의 스크롤 경험을 향상시키기 위한 스타일 및 동작 조정
-
- 가로 모드 지원
-
태블릿이나 모바일 기기의 가로 모드에서도 적절히 표시되도록 레이아웃 조정
-
터치 친화적인 요소 크기를 위한 추가 CSS 코드를 확인하자.
@media (max-width: 480px) {
/* 기존 미디어 쿼리 내용 */
.option-item {
padding: 12px 16px;
}
.checkbox {
width: 20px;
height: 20px;
}
.dropdown-header {
min-height: 44px; /* 애플의 권장 터치 타겟 크기 (44x44px) */
}
.tag-close svg {
width: 16px;
height: 16px;
}
.search-input input {
font-size: 16px; /* iOS에서 자동 확대 방지 */
padding: 8px 0;
}
}
이제 우리는 필터링 기능이 있는 드롭다운 메뉴를 성공적으로 구현했다.
이 컴포넌트는 다양한 웹사이트나 애플리케이션에서 활용할 수 있으며, 사용자가 많은 양의 데이터를 효과적으로 필터링하고 탐색할 수 있게 도와준다.
필요에 따라 다양한 방식으로 확장하고 커스터마이징할 수 있다:
- 다중 필터 적용
-
여러 드롭다운 필터를 조합하여 더 복잡한 필터링 시나리오 구현
-
- 데이터 저장
-
localStorage나 쿠키를 사용하여 사용자의 필터 선택을 저장하고 다음 방문 시 복원
-
- 서버 측 필터링
-
대규모 데이터셋의 경우 AJAX를 사용하여 서버 측 필터링 구현
-
- 애니메이션 효과
-
CSS 트랜지션이나 애니메이션을 추가하여 사용자 경험 향상
-
필터링 기능이 있는 드롭다운 메뉴는 웹사이트의 사용자 경험을 크게 향상시키는 중요한 UI 요소다.
이 글에서 설명한 HTML, CSS, JavaScript 기법을 활용하면 사용자 친화적이고 기능적인 드롭다운 필터를 구현할 수 있다.
다양한 데이터 필터링 요구사항에 맞게 이 코드를 확장하고 커스터마이징하여 자신의 웹 프로젝트에 적용해보자.