作者:刘锦泉
一、客户的反馈
小刘是公司的一名前端工程师。某天,他在业务反馈群里看到客户抱怨:
“xx 页面用方向键居然滚不动了!”
客户还补充:
“前两天还好好的,今天突然就不行了。鼠标滚轮正常、拖动滚动条正常,偏偏就是键盘方向键没反应。”
小刘立刻响应,同时心里纳闷:
“奇怪,方向键滚动是浏览器的默认行为啊,怎么会突然坏掉?难道有人动过它?”
为了弄清楚,他决定亲自重现场景。
二、初步排查
接到反馈后,小刘首先排除了兼容性问题。
打开页面一看,果然和客户描述一致:滚轮、拖动条正常,方向键没反应。
他皱起眉头:
“这可不是常见的问题啊……”
小刘随即翻看了发布记录,本以为是最近上线的功能导致,结果发现近期并没有相关发布。
“没发版,怎么会突然坏掉呢?”
为了进一步确认,他又在本地拉起项目。结果方向键一切正常。
线上不行,本地正常——情况更加诡异了。
他开始怀疑:问题不在业务逻辑,而是和 运行环境 有关。
很快,他联想到团队最近刚上线的 微前端多 Tab 缓存架构。
“会不会是多 Tab 把键盘事件或者焦点劫持了?”
带着疑问,他继续深入排查。。
三、锁定元凶
经过一层层调试,小刘把范围缩小到 Antd Tabs 组件
为了验证,他单独写了一个demo(codesandbox.io/p/sandbox/j…) 包裹内容区并设置滚动。
结果问题果然复现:只要页面在 Tabs 内,方向键就会失灵。
小刘眼前一亮:
“果然,罪魁祸首就是 Tabs组件!”
但他没有急着下结论,而是去社区翻阅资料。果不其然,在在 Ant Design GitHub issues (github.com/ant-design/…) 中,他找到了几乎一致的反馈。
原来,Antd Tabs 在渲染时,会给每个 TabPane
设置 tabindex=0
来管理焦点切换。但这带来一个副作用:
当用户点击某些非可聚焦元素时,焦点会“掉到”外层这个 div 上,从而劫持方向键,导致页面无法滚动:
小刘恍然大悟:
“原来是
tabindex
在背后搞鬼啊!之前还真没怎么注意过这个属性。”
于是,他决定彻底弄清楚 tabindex
的原理。。
四、问题原理解析
1.焦点与可聚焦元素
在应用程式型页面中,往往存在许多需要用户交互的元素。焦点(Focus) 是键盘交互的核心机制,它决定了用户当前能操作的对象。
在 HTML 中,元素可分为 可聚焦(focusable) 与 不可聚焦。常见的默认可聚焦元素包括(可聚焦元素详细兼容性(allyjs.io/data-tables…) ):
<a>
标签(带href
属性)<link>
标签(带href
属性)<button>
<input>
(排除type="hidden"
)<select>
<textarea>
这些元素天然支持一下能力:
- 键盘聚焦(通过
Tab
键切换) - 脚本聚焦(通过
element.focus()
)
👉 可以通过 document.activeElement
查看当前获得焦点的元素:
document.querySelector("a").focus();
console.log(document.activeElement); // 当前聚焦的元素
2.tabindex 是什么
tabindex
是所有 HTML 元素的通用属性,用于控制:
- 元素是否能被聚焦
- Tab 键导航的顺序(通常使用Tab键,因此得名)
非聚焦元素设置 tabindex
属性即可拥有可聚焦元素的能力,示例:
<div tabindex="0"> 非聚焦元素,使用 tabindex 聚焦</div>
效果:
3.tabindex 的使用
tabindex 的最大值不应超过 32767。如果没有指定,它的默认值为 0。
tabindex
接受一个整数作为值,具有不同的结果,具体取决于整数的值。
tabindex="0"
- 元素可聚焦
- 焦点顺序按照 DOM 的排列顺序
tabindex="-1"
- 元素不可通过
Tab
键导航获得焦点 - 但仍可通过 JavaScript
focus()
主动聚焦
- 元素不可通过
tabindex="1"
或更大值- 元素可通过
Tab
键获得焦点 - 焦点顺序由开发者手动指定
- ❌ 这种做法会打乱默认顺序,是一种 反模式(anti-pattern),应避免使用
- 元素可通过
👉 在实际开发中,应 仅使用 0
和 -1
。
五、解决方案与实践
小刘最终总结出几种方案:
- 去掉 Tabs 的
tabindex**
- 优点:方向键立刻恢复正常
- 缺点:容器不能再通过 Tab 键聚焦
- 为滚动容器设置
tabindex="0"
- 优点:可通过 Tab 聚焦后,用方向键滚动
- 缺点:需要所有滚动容器都加
tabindex
,代价过大
经过评估,小刘最终选择:去掉 Tabs 的 tabindex
。
这样不仅缩小了影响面,还能保证子应用页面任何位置都能正常使用方向键滚动。
六、终篇
一次看似离奇的“方向键失灵”,最终真凶竟是一个小小的 tabindex
。
小刘从这次经历中总结出三点经验:
- 键盘焦点管理的重要性:不仅要考虑鼠标用户,也要考虑键盘用户和无障碍场景。
- 合理使用
tabindex
:0
用于需要聚焦的容器,-1
用于仅脚本控制,避免大于 1。 - 多关注社区:很多“诡异问题”,社区里可能早有人遇到。
最终,小刘把整个排查过程和 tabindex
的知识点记录下来,分享给团队和社区:
细节决定体验,一个小小的属性,可能改变整个页面的交互。