也许这完全是一个坏主意,我很乐意将其作为反馈;但是,我正在寻找一种控制文档上的tabbable元素的好方法。
要求:
注意:这是个人思想实验,不确定这是不是一个好主意。将采取任何类型的线索:文章,图书馆,代码片段,但显然最好的将是满足所有要求的答案(或任何其他你可以提出的想法)!
button {
padding: 6px 8px;
margin: 12px;
}
.focus-trap{
padding: 24px;
background: rgba(150, 150, 48, .5);
}
.now-tabbable {
display: inline-flex;
padding: 15px 8px;
background: pink;
margin: 12px;
min-width: 45px;
justify-content: center;
}
<!--
.focus-traps:
- Are meant to declare that tab focus are order by the programmer.
- Focus-traps can be cyclical. currently denoted by class but up for any ideas data-attr etc.
.now-tabbable:
- Are meant to declare that this element is part of the tab flow.
-->
<div class="focus-trap">
<button>ZERO × escape</button>
<button>two</button>
<button>one</button>
<button>three</button>
<hr>
<div class="focus-trap cyclical">
<h1>Trap focus in here until escape</h1>
<button>four - B</button>
<button>four - C</button>
<button>four - A × escape</button>
</div>
<div class="now-tabbable">seven</div>
<div class="now-tabbable">five</div>
<div class="now-tabbable">six</div>
</div>
一般来说,你应该避免使用焦点陷阱,除了模态上下文,比如模态对话。
这里建议的解决方案将建立一个带有roving tab index的焦点组,您可以通过箭头键在其中导航焦点。然后TAB将离开该组。
但请记住,您只将焦点组用于可以预期行为的UI模式。 ARIA标准提及Keyboard Navigation Inside Components:
[...]强烈建议使用与§ 3. Design Patterns and Widgets中演示的常见GUI操作系统中相似的组件相同的键绑定。
例如,您也可以通过一种明确形成一组的方式直观地格式化按钮,类似于Toolbar。
class focusGroup {
constructor(el, cyclical: false) {
this.cyclical = cyclical;
// store children sorted by data-tabindex attribute
this.children = Array.from(el.querySelectorAll('[data-tabindex]')).sort((el1, el2) => el1.getAttribute('data-tabindex') > el2.getAttribute('data-tabindex'));
// remove tab index for all children
this.children.forEach(childEl => childEl.setAttribute('tabindex', '-1'));
// make first child tabbable
this.children[0].setAttribute('tabindex', '0');
this.i = 0;
// bind arrow keys
el.addEventListener('keyup', e => {
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') this.next();
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') this.prev();
});
}
next() {
if (this.i < this.children.length -1) this.i += 1
else if (this.cyclical) this.i = 0;
this.updateFocus();
}
prev() {
if (this.i > 0) this.i -= 1
else if (this.cyclical) this.i = this.children.length -1;
this.updateFocus();
}
updateFocus() {
this.children.forEach(el => el.setAttribute('tabindex', '-1'));
this.children[this.i].setAttribute('tabindex', '0');
this.children[this.i].focus();
}
}
document.querySelectorAll('.focus-trap:not(.cyclical)').forEach(el => new focusGroup(el));
document.querySelectorAll('.focus-trap.cyclical').forEach(el => new focusGroup(el, true));
button {
padding: 6px 8px;
margin: 12px 0;
}
.focus-trap{
padding: 24px;
background: rgba(150, 150, 48, .5);
}
.now-tabbable {
display: inline-flex;
padding: 15px 8px;
background: pink;
margin: 12px;
min-width: 45px;
justify-content: center;
}
<!--
.focus-traps:
- Are meant to declare that tab focus are order by the programmer.
- Focus-traps can be cyclical. currently denoted by class but up for any ideas data-attr etc.
.now-tabbable:
- Are meant to declare that this element is part of the tab flow.
-->
<div class="focus-trap">
<button data-tabindex="0">ZERO × escape</button>
<button data-tabindex="2">two</button>
<button data-tabindex="1">one</button>
<button data-tabindex="3">three</button>
</div>
<div class="focus-trap cyclical">
<button data-tabindex>four - B</button>
<button data-tabindex>four - C</button>
<button data-tabindex>four - A × escape</button>
</div>
<div>
<button class="now-tabbable">seven</button>
<div class="now-tabbable">five</div>
<div class="now-tabbable">six</div>
</div>
Api用于控制带有DOM顺序的标签顺序。
然后,视觉顺序将需要与焦点顺序对齐。在示例代码中,您可以使用data-tabindex="i"
来控制
聚焦陷阱可能是周期性的,必须明确地转义。
您可以调用该类并提供第二个参数true
以建立包裹或循环顺序。
一种将非可列表元素放入制表符流的方法。
只有具有data-tabindex
属性的元素才是可聚焦的。
没有正的tabIndex值。
你需要使用正data-tabindexes
,但示例代码将始终只使用tabindex="0"
使一个元素可聚焦。这意味着如果您通过制表符重新进入组,最后一个焦点元素将再次被聚焦。