如何在Lit Web Component的Shadow DOM中手动设置元素焦点
解决Lit自定义下拉组件的焦点控制与焦点陷阱问题
一、手动切换焦点到首个下拉项
要让元素获得焦点,核心是确保元素可聚焦后调用focus()方法,具体步骤:
让下拉项具备可聚焦特性
优先用<button>作为下拉项(自带可聚焦特性+语义化,更符合无障碍要求);如果坚持用<div>,必须给元素添加tabindex="0",示例:<div id="dropdown-panel"> <!-- 用button的方式 --> <button id="first-dropdown-item" class="dropdown-item">选项1</button> <!-- 用div的方式 --> <div id="first-dropdown-item" class="dropdown-item" tabindex="0">选项1</div> </div>打开面板后设置焦点
由于Lit更新DOM后需要等待渲染完成才能获取到元素,建议用requestAnimationFrame延迟执行焦点设置,避免元素未挂载导致的报错:openDropdown() { this.isOpen = true; // 等待DOM渲染完成后聚焦首个选项 requestAnimationFrame(() => { const firstItem = this.renderRoot.querySelector('#first-dropdown-item'); firstItem?.focus(); // 可选链防止元素不存在时报错 }); }
二、实现下拉面板内的焦点陷阱
焦点陷阱的核心是让焦点在面板内循环,无法跳出到外部元素,同时支持Esc键关闭面板并返回焦点到触发元素,实现逻辑如下:
监听面板的键盘事件
在组件的firstUpdated生命周期中绑定键盘事件(确保DOM已渲染完成):firstUpdated() { const panel = this.renderRoot.querySelector('#dropdown-panel'); panel.addEventListener('keydown', this.handlePanelKeydown.bind(this)); }处理键盘交互逻辑
针对Tab/Shift+Tab、上下箭头、Esc键分别处理:handlePanelKeydown(e) { const items = Array.from(this.renderRoot.querySelectorAll('.dropdown-item')); const currentIndex = items.findIndex(item => item === document.activeElement); switch(e.key) { // 处理Tab键循环 case 'Tab': if (e.shiftKey) { // Shift+Tab:当前是首个元素时,跳转到最后一个 if (currentIndex === 0) { e.preventDefault(); items[items.length - 1].focus(); } } else { // 正常Tab:当前是最后一个元素时,跳转到首个 if (currentIndex === items.length - 1) { e.preventDefault(); items[0].focus(); } } break; // Esc键关闭面板,焦点返回触发元素 case 'Escape': this.closeDropdown(); this.renderRoot.querySelector('#trigger-div').focus(); break; // 上下箭头导航选项 case 'ArrowDown': e.preventDefault(); const nextIndex = (currentIndex + 1) % items.length; items[nextIndex].focus(); break; case 'ArrowUp': e.preventDefault(); const prevIndex = (currentIndex - 1 + items.length) % items.length; items[prevIndex].focus(); break; } } closeDropdown() { this.isOpen = false; }
三、完善触发元素的无障碍属性
为了让屏幕阅读器正确识别组件,给触发<div>添加必要的ARIA属性:
<div id="trigger-div" tabindex="0" role="button" aria-haspopup="listbox" aria-expanded="${this.isOpen}" @click=${this.openDropdown} @keydown=${this.handleTriggerKeydown} > 选择选项 </div>
触发元素的键盘事件处理(支持回车/空格打开面板):
handleTriggerKeydown(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); // 阻止默认滚动行为 this.openDropdown(); } }
内容的提问来源于stack exchange,提问作者ineedtoknow
相关产品推荐
相关产品推荐

