You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Vanilla JavaScript为简单下拉菜单添加键盘导航?有无HTML5原生方案?

下拉菜单的键盘导航实现方案

一、原生JavaScript实现方案

首先需要给菜单触发元素添加tabindex属性使其可被键盘聚焦,同时给下拉选项设置tabindex="-1"(让选项可被聚焦但不会被Tab键直接选中)。修改后的HTML结构如下:

<div class="dropdown">
    <div tabindex="0" class="dropdown-trigger">Test Dropdown</div>
    <ul class="dropdown-menu" hidden>
        <li tabindex="-1">A</li>
        <li tabindex="-1">B</li>
        <li tabindex="-1">C</li>
    </ul>
</div>

接下来编写JavaScript逻辑,实现以下核心交互:

  • 回车/空格键:切换菜单的展开/收起状态
  • 上下箭头键:在菜单选项间循环导航
  • ESC键:关闭菜单并将焦点放回触发元素
  • 点击菜单外部:自动关闭菜单
  • 选中选项:执行对应操作并关闭菜单
const dropdown = document.querySelector('.dropdown');
const trigger = dropdown.querySelector('.dropdown-trigger');
const menu = dropdown.querySelector('.dropdown-menu');
const menuItems = menu.querySelectorAll('li');
let currentIndex = -1;

// 切换菜单显示状态
function toggleMenu() {
    menu.hidden = !menu.hidden;
    if (!menu.hidden) {
        currentIndex = 0;
        menuItems[currentIndex].focus();
    }
}

// 循环导航到指定索引的选项
function navigateToIndex(index) {
    if (index < 0) index = menuItems.length - 1;
    if (index >= menuItems.length) index = 0;
    currentIndex = index;
    menuItems[currentIndex].focus();
}

// 点击触发元素切换菜单
trigger.addEventListener('click', toggleMenu);

// 触发元素的键盘事件处理
trigger.addEventListener('keydown', (e) => {
    switch(e.key) {
        case 'Enter':
        case ' ':
            e.preventDefault();
            toggleMenu();
            break;
        case 'ArrowDown':
            e.preventDefault();
            toggleMenu();
            break;
    }
});

// 菜单选项的键盘与点击事件处理
menuItems.forEach((item, index) => {
    item.addEventListener('keydown', (e) => {
        switch(e.key) {
            case 'Enter':
            case ' ':
                e.preventDefault();
                console.log(`选中选项:${item.textContent}`);
                menu.hidden = true;
                trigger.focus();
                break;
            case 'ArrowUp':
                e.preventDefault();
                navigateToIndex(currentIndex - 1);
                break;
            case 'ArrowDown':
                e.preventDefault();
                navigateToIndex(currentIndex + 1);
                break;
            case 'Escape':
                e.preventDefault();
                menu.hidden = true;
                trigger.focus();
                break;
        }
    });

    item.addEventListener('click', () => {
        console.log(`选中选项:${item.textContent}`);
        menu.hidden = true;
        trigger.focus();
    });
});

// 点击外部区域关闭菜单
document.addEventListener('click', (e) => {
    if (!dropdown.contains(e.target)) {
        menu.hidden = true;
    }
});

可以配合基础CSS让交互更直观:

.dropdown {
    position: relative;
    display: inline-block;
}
.dropdown-trigger {
    padding: 8px 12px;
    border: 1px solid #ccc;
    cursor: pointer;
}
.dropdown-menu {
    position: absolute;
    top: 100%;
    left: 0;
    margin: 0;
    padding: 0;
    list-style: none;
    border: 1px solid #ccc;
    background: white;
    min-width: 100px;
}
.dropdown-menu li {
    padding: 8px 12px;
    cursor: pointer;
}
.dropdown-menu li:focus {
    background-color: #eee;
    outline: none;
}

二、HTML5原生解决方案

如果不需要高度自定义的样式,HTML5的<select>和<option>元素本身就自带完整的键盘导航支持,浏览器会自动处理所有交互逻辑(上下箭头选择、回车确认、ESC关闭等),无需额外编写代码:

<select class="native-dropdown">
    <option value="a">A</option>
    <option value="b">B</option>
    <option value="c">C</option>
</select>

注意:原生<select>的样式在不同浏览器中差异较大,难以完全自定义。如果需要兼顾自定义样式和原生键盘支持,可以结合<select>的语义与JavaScript增强,但纯原生方案的优势是开箱即用,无需额外逻辑。


内容的提问来源于stack exchange,提问作者Arnold Zahrneinder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 00:30:54