實現一組水平列表項(<li>)的鍵盤上下鍵循環選中切換,當前項高亮顯示(.curr 類)。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>鍵盤導航原生JS版</title>
<style>
li { float: left; height: 50px; width: 50px; background: #ccc; list-style: none; margin-left: 5px; }
li.curr { background: red; }
</style>
</head>
<body>
<ul id="navList">
<li class="curr"></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<script>
// 工具函數:類名操作
function hasClass(el, cls) {
return el.classList.contains(cls);
}
function addClass(el, cls) {
if (!hasClass(el, cls)) el.classList.add(cls);
}
function removeClass(el, cls) {
if (hasClass(el, cls)) el.classList.remove(cls);
}
// 獲取當前高亮項索引
function getCurrentIndex(items, cls) {
for (let i = 0; i < items.length; i++) {
if (hasClass(items[i], cls)) return i;
}
return -1;
}
// 核心邏輯
document.addEventListener('DOMContentLoaded', () => {
const items = document.querySelectorAll('#navList li');
let currentIndex = getCurrentIndex(items, 'curr');
function move(direction) {
// 移除當前高亮
removeClass(items[currentIndex], 'curr');
// 計算新索引(循環)
currentIndex = (currentIndex + direction + items.length) % items.length;
// 添加新高亮
addClass(items[currentIndex], 'curr');
}
// 鍵盤事件綁定
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowUp' || e.keyCode === 38) {
move(-1); // 上鍵:前一項
} else if (e.key === 'ArrowDown' || e.keyCode === 40) {
move(1); // 下鍵:後一項
}
});
});
</script>
</body>
</html>
classList API 替代正則操作 className,更簡潔高效querySelectorAll 獲取靜態 NodeList,配合索引運算避免 jQuery prevAll 遍歷開銷(index + len) % len 實現雙向循環,邏輯統一DOMContentLoaded 確保 DOM 就緒後綁定事件e.key(現代)與 e.keyCode(舊瀏覽器)MutationObserver 監聽或事件委託aria-selected 無障礙屬性提升可訪問性