You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

点击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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 04:58:19