template shadowrootmode 怎么在闭合阴影根中保留焦点委托
来源:17golang原创
时间:2026-10-13 10:58:30 120浏览 收藏
在声明式闭合阴影根中保留焦点委托,直接把 shadowrootmode="closed" 和布尔属性 shadowrootdelegatesfocus 写在同一个 template 上。根内部还要有可聚焦控件。closed 不会取消委托,但 host.shadowRoot 返回 null,所以不要靠外部读取这个属性来判断委托是否生效。
我更愿意把这件事拆成两个选择:组件内部结构是否公开,以及用户怎样进入输入框。以一个只有姓名输入框的组件为例,前者可以保持闭合,后者仍然交给浏览器原生焦点机制。下面是可自行复现的设计示例,不是已执行的浏览器测试记录。
closed 和 delegatesFocus 是两项独立配置
从脚本创建根迁移到声明式 Shadow DOM,变化是把根的创建配置放入初始 HTML,而不是等 JavaScript 执行 attachShadow。这个能力不要求把模式改成 open。
| 配置 | 负责什么 | 不负责什么 |
|---|---|---|
| shadowrootmode="closed" | 闭合根,宿主 shadowRoot 不暴露该根 | 不禁止内部控件接收焦点 |
| shadowrootdelegatesfocus | 为创建的根启用焦点委托 | 不自动补充控件名称与键盘操作 |
| 内部原生 input | 提供实际可输入、可聚焦对象 | 不依赖外部 querySelector 穿过根 |
我不会把 closed 当成安全隔离工具,也不会为排查方便就直接改为 open。这里要解决的是焦点入口,优先保留原本的封装约定。MDN 对 template 的说明区分了这两项属性;具体焦点目标的选择规则由 HTML 标准定义。

把委托开关放进声明式模板
我会先使用只有一个输入框的片段,避免多个按钮的顺序掩盖问题。将下面内容保存为 HTML,由目标浏览器正常打开,让初始文档解析器处理模板。不要把它作为字符串赋给 innerHTML 后,期待获得同样的声明式根。
闭合根焦点入口示例 请输入希望显示的姓名。
输入框与 label 在同一个根内,用 for 和 id 建立名称关系。宿主的 tabindex="0" 表示参与顺序导航的意图,未使用正数抢占页面顺序。示例没有自动聚焦,不会在页面载入时主动把用户拉离前面的内容。
特别容易写错的是 shadowrootdelegatesfocus="false"。它是布尔属性,字符串 false 并不是关闭开关;要关闭就删除该属性。开关应在根创建之前出现在模板中,不能在模板已被解析后再补属性来修改现有根。
Tab 入口不等于 host.focus()
对我来说,最值得单独讨论的是两种“可聚焦”不是同一件事。调用 host.focus() 是程序化焦点请求;Tab 和 Shift+Tab 则使用顺序焦点导航规则。委托宿主在顺序导航中的处理与直接调用 focus() 不同,不应承诺所有触发方式都永远选中同一个内部节点。
上面的单控件片段把这种差异压到最小:内部只有一个正常可聚焦的 input,使用原生顺序即可。若将来在输入框之前放置关闭按钮,默认委托目标可能改变;即便某个节点的 tabindex="-1" 将其排除在 Tab 顺序之外,它仍可能成为程序化委托的候选。不要把“排除 Tab”理解为“无法聚焦”。
HTML 标准还规定:如果焦点已经位于该宿主内部,再请求聚焦宿主可以保留当前内部目标。因此,“host.focus() 每次都会跳到第一项”也不是可靠接口约定。内部有多个控件时,应明确组件的聚焦语义,而不是让外部猜 DOM 顺序。
有些情形可以用内部 autofocus 影响初始目标,但它可能造成页面载入时跳过重要内容。这个例子刻意不使用它。我更倾向让单输入组件保持原生默认,让复杂组件提供明确的聚焦方法,并另行设计导航规则。

迁移时别把闭合根当成没创建
旧组件若以 if (!host.shadowRoot) 为条件执行 attachShadow,在 open 模式下看起来合理,迁移到 closed 声明式根后却不能证明宿主没有根:闭合根本来就会返回 null。别因此再次创建根或重填模板,导致现有内容被破坏。这个例子使用普通 div 作为宿主,不再混入自定义元素的初始化与水合逻辑。
另一个常见差异是注入方式。普通 innerHTML 不会把其中的声明式模板实例化为 ShadowRoot。若你看到隐藏的 template 而非输入框,应先检查是不是走了字符串片段注入,而不是马上归咎于 closed。不支持该特性的浏览器也可能只得到普通模板,业务页面需要单独安排可用的普通 HTML 降级内容。
我不会把 ShadowRoot.prototype 上存在 delegatesFocus,或者 HTMLTemplateElement 上存在某个属性,当成整个组件在目标环境可用的证明。根创建能力、属性识别和键盘体验需要分别考虑。原生声明式根支持范围与早已存在的命令式委托能力也不能混用版本结论。
用可见焦点判断体验而不是猜内部节点
闭合根的外部观察是有意受限的:当内部控件获得焦点,document.activeElement 通常指向宿主,而非内部 input。外部代码可以判断“焦点在这个组件”,却不能据此断言当前是哪个内部控件。host.shadowRoot 为 null 也不能证明内部没有焦点。
请在自己的目标浏览器与平台中检查以下体验,这里不提供虚构的跨浏览器运行结果:
- 从前置按钮使用 Tab 进入组件,姓名输入框能编辑,焦点轮廓可见。
- 继续 Tab 能离开组件到“下一项”;Shift+Tab 能返回,不出现意外困住焦点的情况。
- 点击“聚焦姓名输入框”,在未处于内部焦点时,输入框能接收文字。
- 闭合根保持不公开,外部读取 shadowRoot 不能当成内部输入框的读取方式。
- 用屏幕阅读器确认输入框名称为“姓名”;不要假定焦点委托本身解决所有可访问性问题。
系统键盘导航设置可能改变某些控件的 Tab 行为,应连同浏览器版本一起记录。若增加 disabled、hidden 或 inert 状态,原先的聚焦目标还可能不再可用,需要把这些状态列入组件自己的交互测试。
我的取舍是:单控件、静态首屏结构适合直接用 closed 加 shadowrootdelegatesfocus;多控件、特殊初始目标或复杂键盘导航则需要组件内的明确焦点接口。封装可以保留,交互语义不能省略。
一定要给宿主加 tabindex 吗?
它不是声明焦点委托的开关。本文给宿主 tabindex="0" 是为了明确键盘入口设计;真正接收输入的仍是内部控件。不要为每一层容器都添加 tabindex,更不要用正数拼接页面导航顺序。
委托后外部 CSS 能选择内部 input 吗?
不能因为委托就穿透阴影边界。把内部焦点提示写在根内,宿主整体提示可以用 :focus-within;别用取消所有 outline 的方式隐藏原生焦点反馈。
能在根创建后修改 delegatesFocus 吗?
ShadowRoot.delegatesFocus 是只读属性。创建阶段配置好,而不是把布尔属性补到已经失去创建作用的模板上。
规范与文档
HTML 焦点处理规范:https://html.spec.whatwg.org/multipage/interaction.html#focus
MDN 声明式模板:https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/template
MDN 焦点委托:https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus
MDN 闭合根读取:https://developer.mozilla.org/en-US/docs/Web/API/Element/shadowRoot
MDN HTML 注入说明:https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
MDN 活动元素:https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement
-
424 收藏
-
130 收藏
-
414 收藏
-
163 收藏
-
137 收藏
-
327 收藏
-
文章 · 前端 | 1小时前 | 前端 · javascript · SyntaxError Web Components customElements.whenDefined Custom Elements 自定义元素名称223 收藏
-
222 收藏
-
440 收藏
-
387 收藏
-
308 收藏
-
222 收藏
-
103 收藏
-
253 收藏
-
158 收藏
-
474 收藏
-
111 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习