如何利用 Shadow DOM 的 delegatesFocus 异步优化复杂嵌套组件的键盘交互

作者:袖梨 2026-06-11
启用 delegatesFocus: true 可解决键盘聚焦断裂问题,需在 attachShadow 时声明且不可后期修改,内部须有唯一可聚焦元素,并手动同步 :focus 状态以支持无障碍。

直接启用 delegatesFocus: true 就能解决大部分键盘聚焦断裂问题——它让外部点击或 Tab 导航自动落到 Shadow DOM 内第一个可聚焦元素上,无需手动调用 focus() 或监听事件做中转。但“异步优化”不是指延迟聚焦,而是指在组件动态加载、条件渲染或嵌套层级较深时,确保焦点委托行为稳定触发、状态同步及时、不被生命周期打断。

必须在 attachShadow 时声明 delegatesFocus

该选项不可后期补设,也不能通过属性动态切换。一旦 shadow root 创建完成,delegatesFocus 值即锁定:

  • ✅ 正确写法:this.attachShadow({ mode: 'open', delegatesFocus: true })
  • ❌ 错误写法:const shadow = this.attachShadow({ mode: 'open' }); shadow.delegatesFocus = true(无效)
  • ❌ 错误写法:在 connectedCallback 中反复调用 attachShadow(会报错或覆盖已有 shadow)

确保内部有且仅有一个“默认可聚焦靶点”

浏览器只在首次聚焦时查找第一个可聚焦元素。若组件结构异步生成(如 slot 内容延后插入、if 条件块初始为空),可能造成委托失败:

  • 显式设置 <input tabindex="0"><button tabindex="0">,比依赖 autofocus 更可靠
  • 避免多个元素同时设 tabindex="0",否则行为不可预测
  • 若主输入是异步挂载的(例如下拉搜索框展开后才插入 <input>),需在插入后手动触发一次 input.focus(),但仅限首次;后续仍靠 delegatesFocus 自动接管

聚焦状态需主动同步到宿主元素

仅开启 delegatesFocus 不足以支撑视觉反馈和逻辑判断。宿主元素本身不会自动获得 :focus 状态,必须手动映射:

  • 在 shadow 内部 <input> 上监听 focusblur 事件
  • 在事件处理器中调用 this.toggleAttribute('focused', e.type === 'focus')
  • 用 CSS 写 :host([focused]) { outline: 2px solid #0078d4; } 实现无障碍轮廓
  • 不要监听 focusin/focusout 到宿主上——这些事件不穿透 shadow boundary

复杂嵌套场景下的注意事项

当自定义组件层层包裹(如 <my-form> → <my-field> → <my-input>),焦点委托仍有效,但需注意:

  • 每一层都应启用 delegatesFocus: true,否则焦点会在某一层卡住
  • 避免在中间层 shadow 中放置多个可聚焦元素却未明确 tabindex 顺序,导致 Tab 键跳转混乱
  • 模态框、下拉菜单等浮层组件必须启用该选项,否则键盘用户无法进入内容区,违反 WCAG 2.1 “键盘可操作”要求
  • 若组件使用 mode: 'closed',则无法通过 JS 访问 shadow 内部,调试困难;生产环境可用,但开发阶段建议保持 'open'

相关文章

精彩推荐