Shadow DOM中tabindex="0"的按钮为何晚于tabindex="10"的普通按钮获得焦点?如何修复?
兄弟,我明白你为啥困惑了——按道理宿主元素#shdow在主DOM的button前面,Shadow里的按钮应该先被Tab聚焦才对,但实际却反过来了。这其实是浏览器Tab遍历的优先级规则在搞鬼,咱们一步步理清楚:
问题原因
你可能对浏览器的Tab遍历顺序规则不太清楚,它是分梯队来的:
- 第一梯队:带正tabindex值的元素
所有设置了正数值tabindex的元素(比如你主DOM里的tabindex="10"),不管它们在DOM树里的位置(哪怕在主DOM末尾),也不管是不是在Shadow DOM中,都会被优先遍历,而且严格按tabindex的数值从小到大排序。 - 第二梯队:tabindex=0或默认可聚焦元素
这时候浏览器才会按照元素在文档流中的自然顺序来遍历——包括Shadow DOM里的元素会跟随宿主在主DOM的位置,插入到遍历序列中。
你的情况正好踩中了这个规则:主DOM的按钮属于第一梯队,Shadow里的按钮属于第二梯队,哪怕宿主在前面,浏览器也会先遍历完所有第一梯队的元素,才会轮到第二梯队,所以就出现了你看到的“主DOM按钮先聚焦”的结果。
修复方案
根据你的需求,给你几个实用的解决办法:
方案1:去掉主DOM按钮的正tabindex(推荐)
如果你希望Tab顺序符合文档流的自然顺序(Shadow按钮在前,主DOM按钮在后),只需要把主DOM按钮的tabindex="10"去掉,或者改成tabindex="0"(默认可聚焦元素的tabindex就是0,不写也一样):
<!-- 修改后的主DOM按钮 --> <button>focus me </button> <!-- 或者显式写tabindex="0" --> <button tabindex="0">focus me </button>
修改后两个按钮都属于第二梯队,浏览器会按文档流顺序:先聚焦Shadow里的按钮(宿主#shdow在主DOM按钮前面),再聚焦主DOM的按钮,完全符合你的预期。
方案2:给Shadow里的按钮设置更小的正tabindex
如果你一定要保留主DOM按钮的tabindex="10",同时想让Shadow里的按钮先被聚焦,可以给Shadow里的按钮设置一个比10小的正tabindex,比如tabindex="5":
document.querySelector('#shdow').attachShadow({mode: 'open'}).innerHTML = ` <style> button{ padding: 1%; border-radius: 10px; width: 100px; height: 50px; } button:focus { background-color: green; } </style> <button tabindex="5">focus me </button>`;
这时候两个按钮都属于第一梯队,浏览器会按tabindex数值从小到大遍历,先聚焦Shadow里的按钮,再聚焦主DOM的按钮。
方案3:手动控制焦点(复杂场景可选)
如果你的场景更复杂,还可以通过JavaScript手动控制焦点切换,利用Shadow DOM作为独立焦点范围的特性,但这个方案需要额外的事件监听逻辑,对于你的简单场景来说没必要,前两个方案完全够用。
备注:内容来源于stack exchange,提问作者ShobiDobi

