JavaScript/CSS 下拉菜单搜索功能
实现一个带搜索功能的下拉菜单(Dropdown with Search)是 Web 开发中常见的交互需求,允许用户从下拉列表中选择选项,并通过输入关键字过滤选项。以下是详细的中文讲解,介绍如何使用 JavaScript 和 CSS 实现下拉菜单搜索功能,包含完整代码示例、样式美化、功能说明和注意事项。
1. 功能需求
- 目标:创建一个可搜索的下拉菜单,用户可以:
- 点击显示/隐藏下拉列表。
- 在输入框中键入关键字,实时过滤选项。
- 选择选项后显示选中值。
- 功能:
- 输入框支持动态过滤(大小写不敏感)。
- 键盘和鼠标交互支持。
- 样式美观,响应式设计。
- 视觉反馈(如高亮选中项)。
- 技术:
- JavaScript:处理输入事件、过滤逻辑、DOM 操作。
- CSS:美化下拉菜单、输入框和选项样式。
2. 实现步骤
2.1 HTML 结构
- 使用
<div>
包装下拉菜单,包含输入框和选项列表。 - 选项使用
<ul>
和<li>
实现,支持动态过滤。 - 示例结构:
<div class="dropdown">
<input type="text" class="search-input" placeholder="搜索...">
<ul class="dropdown-list">
<li data-value="apple">Apple</li>
<li data-value="banana">Banana</li>
<li data-value="cherry">Cherry</li>
<li data-value="date">Date</li>
<li data-value="grape">Grape</li>
</ul>
<div class="selected-value">未选择</div>
</div>
- 说明:
data-value
存储选项值,便于程序处理。.selected-value
显示当前选中项。<input>
用于搜索过滤。
2.2 CSS 样式
- 美化输入框、下拉列表和选项。
- 添加交互效果(如悬停、选中高亮、隐藏/显示列表)。
- 示例 CSS:
.dropdown {
position: relative;
width: 300px;
margin: 20px auto;
font-family: Arial, sans-serif;
}
.search-input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
}
.search-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 5px rgba(0, 123, 255, 0.3);
}
.dropdown-list {
list-style: none;
padding: 0;
margin: 0;
border: 1px solid #ddd;
border-radius: 4px;
max-height: 200px;
overflow-y: auto;
position: absolute;
width: 100%;
background: #fff;
display: none;
z-index: 1000;
}
.dropdown-list.show {
display: block;
}
.dropdown-list li {
padding: 10px;
cursor: pointer;
transition: background 0.2s;
}
.dropdown-list li:hover {
background: #f0f0f0;
}
.dropdown-list li.selected {
background: #007bff;
color: white;
}
.selected-value {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
background: #f9f9f9;
}
- 说明:
.dropdown
使用相对定位,.dropdown-list
使用绝对定位实现下拉效果。max-height
和overflow-y
限制列表高度,添加滚动条。.show
类控制列表显示/隐藏。- 交互样式:输入框聚焦、选项悬停和选中效果。
2.3 JavaScript 逻辑
- 逻辑:
- 输入框监听输入事件,实时过滤选项。
- 点击输入框显示下拉列表,点击外部隐藏。
- 点击选项更新选中值并隐藏列表。
- 支持键盘导航(上/下箭头、Enter 选择)。
- 代码示例:
document.addEventListener('DOMContentLoaded', () => {
const dropdown = document.querySelector('.dropdown');
const input = dropdown.querySelector('.search-input');
const list = dropdown.querySelector('.dropdown-list');
const items = list.querySelectorAll('li');
const selectedValue = dropdown.querySelector('.selected-value');
let focusedIndex = -1;
// 显示/隐藏下拉列表
input.addEventListener('focus', () => {
list.classList.add('show');
filterItems();
});
// 点击外部隐藏列表
document.addEventListener('click', (e) => {
if (!dropdown.contains(e.target)) {
list.classList.remove('show');
focusedIndex = -1;
updateFocus();
}
});
// 过滤选项
input.addEventListener('input', filterItems);
function filterItems() {
const query = input.value.toLowerCase();
items.forEach(item => {
const text = item.textContent.toLowerCase();
item.style.display = text.includes(query) ? '' : 'none';
});
focusedIndex = -1;
updateFocus();
}
// 选择选项
items.forEach((item, index) => {
item.addEventListener('click', () => {
selectedValue.textContent = item.textContent;
input.value = item.textContent;
list.classList.remove('show');
focusedIndex = -1;
updateFocus();
});
});
// 键盘导航
input.addEventListener('keydown', (e) => {
const visibleItems = Array.from(items).filter(item => item.style.display !== 'none');
if (e.key === 'ArrowDown') {
e.preventDefault();
focusedIndex = Math.min(focusedIndex + 1, visibleItems.length - 1);
updateFocus();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
focusedIndex = Math.max(focusedIndex - 1, -1);
updateFocus();
} else if (e.key === 'Enter' && focusedIndex >= 0) {
e.preventDefault();
const selectedItem = visibleItems[focusedIndex];
selectedValue.textContent = selectedItem.textContent;
input.value = selectedItem.textContent;
list.classList.remove('show');
focusedIndex = -1;
updateFocus();
}
});
// 更新焦点样式
function updateFocus() {
items.forEach((item, index) => {
item.classList.remove('selected');
if (index === focusedIndex && item.style.display !== 'none') {
item.classList.add('selected');
item.scrollIntoView({ block: 'nearest' });
}
});
}
});
3. 完整代码示例
以下是一个完整的 HTML 文件,包含 HTML、CSS 和 JavaScript,实现带搜索功能的下拉菜单:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>下拉菜单搜索</title>
<style>
.dropdown {
position: relative;
width: 300px;
margin: 20px auto;
font-family: Arial, sans-serif;
}
.search-input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
}
.search-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 5px rgba(0, 123, 255, 0.3);
}
.dropdown-list {
list-style: none;
padding: 0;
margin: 0;
border: 1px solid #ddd;
border-radius: 4px;
max-height: 200px;
overflow-y: auto;
position: absolute;
width: 100%;
background: #fff;
display: none;
z-index: 1000;
}
.dropdown-list.show {
display: block;
}
.dropdown-list li {
padding: 10px;
cursor: pointer;
transition: background 0.2s;
}
.dropdown-list li:hover {
background: #f0f0f0;
}
.dropdown-list li.selected {
background: #007bff;
color: white;
}
.selected-value {
margin-top: 10px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
background: #f9f9f9;
}
</style>
</head>
<body>
<div class="dropdown">
<input type="text" class="search-input" placeholder="搜索...">
<ul class="dropdown-list">
<li data-value="apple">Apple</li>
<li data-value="banana">Banana</li>
<li data-value="cherry">Cherry</li>
<li data-value="date">Date</li>
<li data-value="grape">Grape</li>
<li data-value="orange">Orange</li>
<li data-value="peach">Peach</li>
</ul>
<div class="selected-value">未选择</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const dropdown = document.querySelector('.dropdown');
const input = dropdown.querySelector('.search-input');
const list = dropdown.querySelector('.dropdown-list');
const items = list.querySelectorAll('li');
const selectedValue = dropdown.querySelector('.selected-value');
let focusedIndex = -1;
// 显示/隐藏下拉列表
input.addEventListener('focus', () => {
list.classList.add('show');
filterItems();
});
// 点击外部隐藏列表
document.addEventListener('click', (e) => {
if (!dropdown.contains(e.target)) {
list.classList.remove('show');
focusedIndex = -1;
updateFocus();
}
});
// 过滤选项
input.addEventListener('input', filterItems);
function filterItems() {
const query = input.value.toLowerCase();
items.forEach(item => {
const text = item.textContent.toLowerCase();
item.style.display = text.includes(query) ? '' : 'none';
});
focusedIndex = -1;
updateFocus();
}
// 选择选项
items.forEach((item, index) => {
item.addEventListener('click', () => {
selectedValue.textContent = item.textContent;
input.value = item.textContent;
list.classList.remove('show');
focusedIndex = -1;
updateFocus();
});
});
// 键盘导航
input.addEventListener('keydown', (e) => {
const visibleItems = Array.from(items).filter(item => item.style.display !== 'none');
if (e.key === 'ArrowDown') {
e.preventDefault();
focusedIndex = Math.min(focusedIndex + 1, visibleItems.length - 1);
updateFocus();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
focusedIndex = Math.max(focusedIndex - 1, -1);
updateFocus();
} else if (e.key === 'Enter' && focusedIndex >= 0) {
e.preventDefault();
const selectedItem = visibleItems[focusedIndex];
selectedValue.textContent = selectedItem.textContent;
input.value = selectedItem.textContent;
list.classList.remove('show');
focusedIndex = -1;
updateFocus();
}
});
// 更新焦点样式
function updateFocus() {
items.forEach((item, index) => {
item.classList.remove('selected');
if (index === focusedIndex && item.style.display !== 'none') {
item.classList.add('selected');
item.scrollIntoView({ block: 'nearest' });
}
});
}
});
</script>
</body>
</html>
4. 代码说明
- HTML:
.dropdown
包含输入框、选项列表和选中值显示。<ul>
包含多个<li>
,每个<li>
有data-value
属性。.selected-value
显示当前选择。- CSS:
- 输入框:聚焦效果、边框圆角。
- 下拉列表:绝对定位、滚动条、隐藏/显示控制。
- 选项:悬停高亮、选中蓝色背景、平滑过渡。
- JavaScript:
- 事件监听:
- 输入框
focus
显示列表。 input
事件实时过滤选项(大小写不敏感)。- 点击外部隐藏列表。
- 点击选项更新选中值。
- 输入框
- 过滤逻辑:使用
includes
匹配输入关键字,动态设置display
属性。 - 键盘导航:
- 上/下箭头切换焦点。
- Enter 键确认选择。
scrollIntoView
确保选中项可见。
- 焦点管理:
focusedIndex
跟踪键盘选中的选项,动态更新.selected
样式。
5. jQuery 实现(可选)
如果项目已使用 jQuery,可以简化 DOM 操作。以下是 jQuery 版本的代码:
$(document).ready(function() {
const $dropdown = $('.dropdown');
const $input = $('.search-input');
const $list = $('.dropdown-list');
const $items = $list.find('li');
const $selectedValue = $('.selected-value');
let focusedIndex = -1;
// 显示列表
$input.on('focus', () => {
$list.addClass('show');
filterItems();
});
// 点击外部隐藏
$(document).on('click', (e) => {
if (!$dropdown.is(e.target) && !$dropdown.has(e.target).length) {
$list.removeClass('show');
focusedIndex = -1;
updateFocus();
}
});
// 过滤选项
$input.on('input', filterItems);
function filterItems() {
const query = $input.val().toLowerCase();
$items.each(function() {
const text = $(this).text().toLowerCase();
$(this).css('display', text.includes(query) ? '' : 'none');
});
focusedIndex = -1;
updateFocus();
}
// 选择选项
$items.on('click', function() {
$selectedValue.text($(this).text());
$input.val($(this).text());
$list.removeClass('show');
focusedIndex = -1;
updateFocus();
});
// 键盘导航
$input.on('keydown', function(e) {
const $visibleItems = $items.filter(':visible');
if (e.key === 'ArrowDown') {
e.preventDefault();
focusedIndex = Math.min(focusedIndex + 1, $visibleItems.length - 1);
updateFocus();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
focusedIndex = Math.max(focusedIndex - 1, -1);
updateFocus();
} else if (e.key === 'Enter' && focusedIndex >= 0) {
e.preventDefault();
const $selectedItem = $visibleItems.eq(focusedIndex);
$selectedValue.text($selectedItem.text());
$input.val($selectedItem.text());
$list.removeClass('show');
focusedIndex = -1;
updateFocus();
}
});
function updateFocus() {
$items.removeClass('selected');
if (focusedIndex >= 0) {
const $visibleItems = $items.filter(':visible');
$visibleItems.eq(focusedIndex).addClass('selected').get(0).scrollIntoView({ block: 'nearest' });
}
}
});
- 依赖:需引入 jQuery:
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
6. 方法对比
方法/特点 | 描述 | 优点 | 缺点 |
---|---|---|---|
原生 JavaScript | 使用 DOM 操作和事件处理实现 | 无依赖,性能好,灵活 | 代码稍复杂 |
jQuery | 使用 jQuery 简化 DOM 和事件处理 | 代码简洁,适合 jQuery 项目 | 需引入 jQuery,增加加载时间 |
第三方库(如 Select2) | 使用现成库实现搜索下拉 | 功能丰富,开箱即用 | 体积大,可能过度复杂 |
Select2 示例(简单配置):
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<select class="search-select">
<option value="apple">Apple</option>
<option value="banana">Banana</option>
<option value="cherry">Cherry</option>
</select>
<script>
$(document).ready(function() {
$('.search-select').select2();
});
</script>
- 说明:Select2 提供开箱即用的搜索下拉,但需额外库。
7. 注意事项
- 输入过滤:
- 使用
toLowerCase
实现大小写不敏感。 - 可扩展支持正则表达式或模糊匹配:
javascript const regex = new RegExp(query, 'i'); item.style.display = regex.test(text) ? '' : 'none';
- 性能:
- 小列表(<100 项):原生或 jQuery 性能足够。
- 大列表(>1000 项):考虑虚拟化(如仅渲染可见选项)。
- 浏览器兼容性:
addEventListener
、querySelector
是 ES5 标准,现代浏览器全支持。scrollIntoView
和includes
兼容性好,IE11 需 polyfill。- 用户体验:
- 键盘导航(箭头、Enter)增强可访问性。
- 添加加载动画(动态数据时)。
- 确保列表宽度与输入框一致。
- 扩展性:
- 支持多选:添加
multiple
属性,调整逻辑存储多个值。 - 动态数据:通过 AJAX 加载选项:
javascript fetch('options.json').then(res => res.json()).then(data => { list.innerHTML = data.map(item => `<li data-value="${item.value}">${item.label}</li>`).join(''); });
- 安全性:
- 用户输入需转义,防止 XSS:
javascript const safeText = input.value.replace(/[<>]/g, '');
8. 总结
- 实现方式:原生 JavaScript + CSS 实现轻量、灵活的搜索下拉菜单。
- 核心逻辑:
- 输入框过滤选项(
includes
)。 - 点击和键盘交互选择选项。
- CSS 提供显示/隐藏和视觉反馈。
- 替代方案:
- jQuery:简化 DOM 操作。
- Select2:功能丰富,适合复杂需求。
- 选择依据:
- 小型项目:原生 JavaScript。
- jQuery 项目:使用 jQuery 简化代码。
- 高级功能:考虑 Select2 或框架集成(如 React Select)。
- 测试:验证过滤、选择、键盘导航,确保样式和交互一致。
如果需要扩展功能(如多选、动态加载、结合 React/Vue),或有其他问题,请提供更多细节,我可以进一步优化回答!