You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

点击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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 16:33:31