点击ARIA树元素时如何阻止焦点事件触发?
问题
根据ARIA树组件规范,当树组件获得键盘焦点且当前无选中元素时,应自动将焦点设置到第一个元素。我已经实现了这个行为,但点击树内元素时,第一个元素会出现焦点闪烁的问题。开发环境为Svelte。
代码示例
JavaScript
function ariaTree(node) { const selectable = node.querySelectorAll('.leaf, summary') function clickHandler(e) { selectable.forEach(item => item.classList.remove('selected')) e.target.classList.add('selected') } selectable.forEach(item => { item.addEventListener('click', clickHandler) }) // ARIA规范:树组件获得焦点且无选中元素时,选中第一个节点 node.addEventListener('focusin', e => { if (node.querySelectorAll('.selected').length === 0) { e.preventDefault() // 阻止焦点自动落到第一个类按钮元素上 selectable[0].classList.add('selected') } }) //return {destroy() {...}} } ariaTree(document.querySelector('ul[role="tree"]'))
CSS
.wrapper { display: flex; &>* {flex: 1} } ul[role=tree] li { list-style-type: none } .selected { background-color: green; color: white; } :where(.leaf, summary):hover { background-color: navy; color: white; }
HTML
<div class="wrapper"> <p> 树组件前面的元素...<br> 按F7启用 caret browsing(会显示当前键盘焦点的光标),点击这段文本后按Tab键进入树组件(第一个元素应被选中/获得焦点)。 <br><br> 刷新页面(树组件中无元素获得焦点),然后点击最后一个元素(world)。此时第一个元素会出现焦点闪烁... </p> <ul role="tree" tabindex="0"> <li class="leaf"> hello </li> <li class="branch"> <details open> <summary>beautiful</summary> <ul> <li class="leaf"> world </li> </ul> </details> </li> </ul> </div>
解决方案
问题原因
点击树内元素时,事件冒泡会先触发ul[role="tree"]的focusin事件,此时clickHandler还未执行,树内没有选中元素,代码会给第一个元素添加selected类;随后clickHandler执行,移除所有元素的selected类并给点击的元素添加该类,这就导致第一个元素短暂被选中,出现闪烁。
修复步骤
修改focusin事件的判断逻辑,区分键盘Tab进入树组件和点击树内元素两种场景:
- 当焦点来自树外部(用户通过Tab键进入),才执行自动选中第一个元素的逻辑;
- 当焦点来自树内部(用户点击树内元素),跳过自动选中逻辑。
修改后的JavaScript代码:
function ariaTree(node) { const selectable = node.querySelectorAll('.leaf, summary') function clickHandler(e) { selectable.forEach(item => item.classList.remove('selected')) e.target.classList.add('selected') } selectable.forEach(item => { item.addEventListener('click', clickHandler) }) node.addEventListener('focusin', e => { // 判断焦点是否来自树组件外部 const isFocusFromOutside = !node.contains(e.relatedTarget); if (node.querySelectorAll('.selected').length === 0 && isFocusFromOutside) { e.preventDefault() selectable[0].classList.add('selected') } }) } ariaTree(document.querySelector('ul[role="tree"]'))
关键改动说明
- 使用
node.contains(e.relatedTarget)判断焦点来源:e.relatedTarget表示触发focusin事件前的焦点元素,如果该元素不在树组件内,说明是从外部Tab进入,此时才执行自动选中第一个元素的逻辑; - 这样就避免了点击树内元素时,
focusin事件误触发第一个元素的选中状态,从根源解决闪烁问题。
内容的提问来源于stack exchange,提问作者thebjorn
相关产品推荐
相关产品推荐

