实现一个带搜索功能的下拉菜单(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-heightoverflow-y 限制列表高度,添加滚动条。
      • .show 类控制列表显示/隐藏。
      • 交互样式:输入框聚焦、选项悬停和选中效果。
2.3 JavaScript 逻辑
  • 逻辑
    1. 输入框监听输入事件,实时过滤选项。
    2. 点击输入框显示下拉列表,点击外部隐藏。
    3. 点击选项更新选中值并隐藏列表。
    4. 支持键盘导航(上/下箭头、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,实现带搜索功能的下拉菜单:

```html 下拉菜单搜索
<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>
```

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 实现大小写不敏感。
    • 可扩展支持正则表达式或模糊匹配:
      const regex = new RegExp(query, 'i');
      item.style.display = regex.test(text) ? '' : 'none';
      
  • 性能
    • 小列表(<100 项):原生或 jQuery 性能足够。
    • 大列表(>1000 项):考虑虚拟化(如仅渲染可见选项)。
  • 浏览器兼容性
    • addEventListenerquerySelector 是 ES5 标准,现代浏览器全支持。
    • scrollIntoViewincludes 兼容性好,IE11 需 polyfill。
  • 用户体验
    • 键盘导航(箭头、Enter)增强可访问性。
    • 添加加载动画(动态数据时)。
    • 确保列表宽度与输入框一致。
  • 扩展性
    • 支持多选:添加 multiple 属性,调整逻辑存储多个值。
    • 动态数据:通过 AJAX 加载选项:
      fetch('options.json').then(res => res.json()).then(data => {
          list.innerHTML = data.map(item => `<li data-value="${item.value}">${item.label}</li>`).join('');
      });
      
  • 安全性
    • 用户输入需转义,防止 XSS:
      const safeText = input.value.replace(/[<>]/g, '');
      

8. 总结

  • 实现方式:原生 JavaScript + CSS 实现轻量、灵活的搜索下拉菜单。
  • 核心逻辑
    • 输入框过滤选项(includes)。
    • 点击和键盘交互选择选项。
    • CSS 提供显示/隐藏和视觉反馈。
  • 替代方案
    • jQuery:简化 DOM 操作。
    • Select2:功能丰富,适合复杂需求。
  • 选择依据
    • 小型项目:原生 JavaScript。
    • jQuery 项目:使用 jQuery 简化代码。
    • 高级功能:考虑 Select2 或框架集成(如 React Select)。
  • 测试:验证过滤、选择、键盘导航,确保样式和交互一致。

如果需要扩展功能(如多选、动态加载、结合 React/Vue),或有其他问题,请提供更多细节,我可以进一步优化回答!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐