寻求类似focusin仅触发一次的事件及焦点陷阱优化方案
解决方案
一、模拟类似mouseenter的focusenter行为
浏览器没有原生的focusenter事件,但可以通过focusin事件结合relatedTarget属性实现相同效果:
focusin事件会冒泡,容器元素会收到子元素的焦点事件- 通过
event.relatedTarget判断焦点是否来自容器外部(既不是容器本身,也不是它的后代元素),如果是,就执行"进入容器"的逻辑
二、修复焦点陷阱的方向问题
你遇到的shift+tab进入时定位错误的问题,需要判断焦点进入的方向:
- 按下
shift+tab时,焦点从容器后方元素进入,应定位到容器内最后一个可聚焦元素 - 普通
tab进入时,定位到第一个可聚焦元素
以下是完整实现代码:
const wrapper = document.getElementById('wrapper'); // 获取容器内所有可聚焦元素 const focusableElements = Array.from(wrapper.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])')); const firstFocusable = focusableElements[0]; const lastFocusable = focusableElements[focusableElements.length - 1]; wrapper.addEventListener('focusin', (e) => { // 判断是否是从外部进入容器(模拟focusenter) const isExternalEnter = !wrapper.contains(e.relatedTarget); if (isExternalEnter) { // 判断进入方向:检查shift键状态 if (e.shiftKey) { // shift+tab进入,聚焦最后一个元素 lastFocusable.focus(); } else { // 普通tab进入,聚焦第一个元素 firstFocusable.focus(); } console.log('进入焦点陷阱'); } }); // 补充完整焦点陷阱的循环逻辑(可选) wrapper.addEventListener('keydown', (e) => { if (e.key === 'Tab') { if (e.shiftKey && document.activeElement === firstFocusable) { e.preventDefault(); lastFocusable.focus(); } else if (!e.shiftKey && document.activeElement === lastFocusable) { e.preventDefault(); firstFocusable.focus(); } } });
#wrapper { display: flex; gap: 20px; padding: 10px; border: 1px solid #ccc; } .item { background: blue; color: white; padding: 8px 16px; text-decoration: none; } .item:focus-visible { outline: red solid 2px; outline-offset: 2px; }
<div id="wrapper"> <a href="" class="item">Item A</a> <a href="" class="item">Item B</a> <a href="" class="item">Item C</a> </div> <!-- 测试用的外部可聚焦元素 --> <button>外部按钮(前)</button> <button>外部按钮(后)</button>
关键逻辑说明
wrapper.contains(e.relatedTarget):检查焦点来源是否在容器内部,返回false则说明是从外部进入e.shiftKey:判断是否按下shift键,以此区分Tab的切换方向- 额外的
keydown事件处理是为了实现完整的焦点循环:在第一个元素按shift+Tab时跳转到最后一个,最后一个按Tab时跳转到第一个
内容的提问来源于stack exchange,提问作者Get Off My Lawn
相关产品推荐
相关产品推荐

