学习前提

理解click、focusout、事件传播及语言选择器示例。

本课完成后你能做什么

复现“按钮能展开但选项点不了”,设计不会抢先移除目标的关闭逻辑。

先理解机制

一次鼠标选择不只是click:指针按下可能改变焦点,随后才有释放和click。某些浏览器或控件在焦点转移时,activeElement会短暂指向body。若focusout里立即隐藏菜单,目标链接可能在click之前消失,最终表现为点击无反应。

relatedTarget表示焦点将要去往的元素,但有时为null。已知目标仍在组件内部时不应关闭;已知目标在外部时可以关闭;null不等于确定离开,应由外部指针或Escape等明确事件处理。不要把一次中间态当作最终用户意图。

把展开和导航职责分开:按钮只改变可见性,选项使用真实链接。外部pointerdown监听先判断contains,内部选项不会被提前关闭。Escape归还焦点,Tab移到外部则关闭。支持原生链接还能保留复制地址与打开新标签等行为。

测试必须包含真实指针路径和键盘路径。element.click直接发起激活,未必重现指针按下与焦点转移,因此只靠这种测试可能漏掉竞态。记录pointerdown、focusout、focusin、click以及relatedTarget,才有依据分析顺序。

可运行示例

运行方式:将HTML示例保存为独立的.html文件并在浏览器打开;含脚本的示例使用现代浏览器。资源文件与多文件模块另按正文要求准备。

<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Honeysuck practice</title></head><body>
<div id="picker"><button id="trigger" type="button" aria-expanded="false" aria-controls="options">Choose section</button>
<div id="options" hidden><a href="#html-topic">HTML</a> <a href="#css-topic">CSS</a></div></div>
<p><a href="#after">Outside link</a></p><h2 id="html-topic">HTML topic</h2><h2 id="css-topic">CSS topic</h2><p id="after">End</p>
<script>
const root=document.querySelector('#picker'),trigger=document.querySelector('#trigger'),options=document.querySelector('#options');
function open(value){options.hidden=!value;trigger.setAttribute('aria-expanded',String(value));}
trigger.addEventListener('click',()=>open(options.hidden));
document.addEventListener('pointerdown',event=>{if(!root.contains(event.target))open(false);});
root.addEventListener('focusout',event=>{if(event.relatedTarget&&!root.contains(event.relatedTarget))open(false);});
root.addEventListener('keydown',event=>{if(event.key==='Escape'){event.preventDefault();open(false);trigger.focus();}});
</script>
</body></html>

推演与验证

鼠标展开后点CSS,地址片段必须变为#css-topic。返回顶部再用键盘Tab进入选项并Enter,结果相同。展开后点击外部关闭;Escape关闭并把焦点放回按钮。给focusout临时记录relatedTarget,对比程序化click和真实点击。

常见错误与边界

不要用任意100毫秒延时掩盖事件顺序,延时只改变竞态窗口。也不要对全部链接preventDefault,那会让导航责任变成你必须完整实现的脚本逻辑。

动手练习

新增按钮ArrowDown打开并聚焦首项;焦点在第二项时按Escape也应返回按钮。列出修复前失败、修复后通过的步骤。

参考解法与理由

在按钮keydown中识别ArrowDown、preventDefault、展开并focus首个链接。只处理需要接管的键,保留Tab原生顺序。验收要确认URL或片段真实改变,而不只检查菜单消失。

进一步阅读