点击Web Component的LightDOM元素时标题为何显示可见焦点?
问题
我开发了一个可自定义按钮的Web Component,需求是点击按钮后让页面标题获得焦点,但不显示可见焦点。目前遇到以下不一致行为:
- 原生button仅含文本、Web Component插槽仅含文本时,标题无可见焦点,符合预期;
- 原生button包含span时,标题仍无可见焦点,但Web Component插槽包含span时,标题出现可见焦点,不符合预期;
- 仅点击Web Component的ShadowDOM部分时无此问题,仅点击LightDOM元素(如span)才会触发;
- 希望标题获得焦点但不显示可见焦点,且不通过强制隐藏(如
outline: none)实现。
代码示例
JavaScript
// Template web component const template = document.createElement('template'); template.innerHTML = ` <style> button { background-color: lightgreen; padding: 10px; } </style> <button> <slot></slot> </button>`; // Class web component class MyBtn extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.shadowRoot.appendChild(template.content.cloneNode(true)); } } // web component definition window.customElements.define('my-btn', MyBtn); // Set focus to title on click function focusTitle() { const h1 = document.getElementById('title'); h1.setAttribute('tabindex', -1); h1.focus(); } // Add click event listener document.getElementById('btn1').addEventListener('click', focusTitle); document.getElementById('btn2').addEventListener('click', focusTitle);
HTML
<h1 id="title">Test redirection focus</h1> <button id="btn1" style="padding: 10px"> <span>Span</span> Bouton </button> <my-btn id="btn2"> <span>Span</span> Bouton </my-btn>
解决方案
问题根源
点击Web Component插槽内的LightDOM元素(如span)时,浏览器会将该元素标记为激活元素(即使它本身不可聚焦),后续调用h1.focus()时,浏览器判定这是用户触发的跨元素焦点转移,因此显示可见焦点。而原生button内的span点击时,button会作为焦点容器拦截焦点,激活元素始终是button,转移到h1时不会显示可见焦点。
修复代码
修改Web Component类,在点击LightDOM元素时主动将焦点转移到ShadowDOM内的button,确保焦点转移路径和原生button一致:
// Template web component const template = document.createElement('template'); template.innerHTML = ` <style> button { background-color: lightgreen; padding: 10px; } </style> <button> <slot></slot> </button>`; // Class web component class MyBtn extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.shadowRoot.appendChild(template.content.cloneNode(true)); this.button = this.shadowRoot.querySelector('button'); } connectedCallback() { // 监听组件内的点击事件 this.addEventListener('click', (e) => { // 若点击的是LightDOM元素,将焦点转移到ShadowDOM的button if (!this.button.contains(e.target)) { this.button.focus(); } }); } } // web component definition window.customElements.define('my-btn', MyBtn); // Set focus to title on click function focusTitle() { const h1 = document.getElementById('title'); h1.setAttribute('tabindex', -1); h1.focus(); } // Add click event listener document.getElementById('btn1').addEventListener('click', focusTitle); document.getElementById('btn2').addEventListener('click', focusTitle);
原理说明
通过在Web Component内监听点击事件,当点击LightDOM元素时先将焦点设置到ShadowDOM的button上,此时激活元素变为button。后续调用h1.focus()时,焦点从可聚焦的button转移到h1,浏览器的焦点可见性逻辑会和原生button的情况保持一致,不会显示h1的可见焦点,完全符合需求。
内容的提问来源于stack exchange,提问作者CHRISTOPHE GARCIA
相关产品推荐
相关产品推荐

