tabindex="-1"使元素可被JavaScript聚焦却不进入Tab顺序,是实现网格键盘导航的基础;需配合preventDefault、焦点单元格预筛选、行列坐标映射及focusin/focusout状态同步来确保健壮性。

tabindex="-1" 是让元素可聚焦但不进Tab顺序的关键
很多开发者误以为 tabindex="-1" 只是“禁用”焦点,其实它恰恰是实现自定义键盘导航的起点:元素能被 element.focus() 主动聚焦,又不会干扰用户默认的 Tab 流。网格(比如表格、卡片列表、日历)里那些需要方向键控制但本身不是表单控件的元素(如 <div class="cell">),必须加 tabindex="-1" 才能被 JavaScript 聚焦。
注意别写成 tabindex="0"——那会让所有网格单元都挤进原生 Tab 链,导致按一次 Tab 就跳一个格,完全失控。
监听 keydown 事件时必须阻止默认行为
方向键(ArrowUp / ArrowDown / ArrowLeft / ArrowRight)在可编辑区域或某些浏览器中自带滚动行为。如果不调用 event.preventDefault(),按方向键可能触发页面滚动或光标移动,而不是网格内跳转。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 只对已聚焦的网格容器(如
grid-container)或当前激活单元格监听keydown - 用
event.key判断方向,避免依赖 keyCode(已废弃) - 确认焦点落在目标单元格后再调用
.focus(),否则可能聚焦失败
计算下一个焦点单元格要避开不可达位置
网格不是无限二维数组,边界、合并单元格、禁用项(aria-disabled="true")、隐藏项(display: none 或 visibility: hidden)都会让“右一格”实际不存在。硬编码索引加减会出错。
更稳的做法是预先收集所有可聚焦单元格(带 tabindex="-1" 且未被禁用),存为数组,再用当前索引 + 方向偏移查表:
const focusableCells = Array.from(
grid.querySelectorAll('[tabindex="-1"]:not([aria-disabled="true"])')
);
const currentIndex = focusableCells.indexOf(document.activeElement);
const nextIndex = Math.max(0, Math.min(focusableCells.length - 1, currentIndex + offset));
focusableCells[nextIndex]?.focus();如果需要严格按行列跳(比如按 ArrowDown 必须到正下方单元格),就得维护行列映射关系,用 getBoundingClientRect() 或 data 属性标记坐标,再筛选同列/同行的最近候选。
focusin 和 focusout 用于同步视觉状态与焦点逻辑
仅靠 tabindex 和 focus() 不足以支撑完整体验。用户用鼠标点一个单元格,或从地址栏按 Tab 进入网格,焦点变化未必走你写的键盘逻辑——这时候得靠 focusin 捕获真实焦点来源,并更新内部激活状态(比如高亮样式、aria-selected)。
容易忽略的点:
-
focusin在冒泡阶段触发,比focus更早,适合拦截并重定向 - 不要在
focusout里直接focus()到别的元素,可能引发焦点循环 - 用
document.hasFocus()配合blur判断是否彻底失焦,清理临时状态
真正的难点不在怎么跳,而在怎么判断“该跳到哪”和“跳完算不算真正落定”——边界、动态增删、多层嵌套容器,都会让坐标计算失效。别省略运行时校验。



















