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

Web Components中Shadow DOM元素聚焦后active element为null问题求助

解决Shadow DOM Web Component焦点设置后activeElement为null的问题

问题描述

在实现my-select自定义Web Component时,调用内部的#focus()方法后,读取document.activeElement返回null,无法正确将焦点设置到Shadow DOM内的目标节点。

组件实现代码

class MySelect extends HTMLElement {

  constructor() {
    super();

    var template = document.getElementById('select-template').content;

    this.attachShadow({ mode: 'open', delegatesFocus: true });
    this.shadowRoot.appendChild(template.cloneNode(true));

    this.selectNode = this.shadowRoot.querySelector('.test-select__select');
    this.iconNode = this.selectNode.getElementsByClassName('test-select__select-icon')[0];
    this.menuNode = this.shadowRoot.querySelector('.test-select__menu');

    this.value = null;
    this.isOpen = false;
  }

  connectedCallback() {
    this.classList.add('cegid-select');
    this.#closeOptions();
    this.selectNode.addEventListener('click', this.#openOptions.bind(this));
  }

  attributeChangedCallback(attrName, oldValue, newValue) {
    if (newValue !== oldValue) {
      this[attrName.replace('data-', '')] = newValue;
    }
  }

  set _value(option) {
    this.value = option.value;
    this.selectNode.getElementsByClassName('test-select__select-text')[0].textContent = option.label;
  }

  set options(options) {
    var menuNode = this.menuNode,
        menuItemNode;

    options.forEach((option) => {
      menuItemNode = document.createElement('div');
      menuItemNode.classList.add('test-select__menu-item');
      menuItemNode.textContent = option.label;
      menuItemNode.addEventListener('click', this.#selectOption.bind(this, option));
      menuNode.append(menuItemNode);
    });

    this._value = options[0];
  }


  #openOptions() {
    this.#focus();

    if (this.isOpen) {
      this.#closeOptions();
    } else {
      this.menuNode.style.display = 'block';
      this.isOpen = true;
    }
  }


  #closeOptions() {
    this.menuNode.style.display = 'none';
    this.isOpen = false;
  }

  #selectOption(option) {
    this._value = option;

    this.dispatchEvent(
      new window.UIEvent('change',  {
        bubbles: true,
        cancelable: false,
        composed: true
      })
    );

    this.#closeOptions();
  }

  focus() {
    this.shadowRoot.querySelector('.test-select__select')
      .focus();
  }

  #focus() {
    this.focus();
  }
}

customElements.define('my-select', MySelect);

解决方案

1. 为目标元素添加可聚焦特性

默认情况下,div等元素不具备获得焦点的能力。需要给.test-select__select元素添加tabindex属性,使其成为可聚焦元素:

  • 方法一:直接在模板中修改元素:
    <div class="test-select__select" tabindex="0">
      <!-- 元素内容 -->
    </div>
    
  • 方法二:在组件构造函数中动态设置:
    constructor() {
      super();
      // ... 现有代码
      this.selectNode.setAttribute('tabindex', '0');
    }
    
    tabindex="0"表示元素可通过Tab键聚焦,且处于正常的Tab导航流中;若只需要通过JS聚焦,可设置为tabindex="-1"。

2. 优化焦点方法的实现

直接使用已缓存的this.selectNode,避免重复查询DOM,同时增加存在性检查:

focus() {
  if (this.selectNode) {
    this.selectNode.focus();
  }
}

3. 验证焦点状态

设置焦点后,可以通过以下方式验证:

  • 当delegatesFocus: true时,document.activeElement会指向<my-select>组件本身,而Shadow DOM内的实际焦点元素可通过this.shadowRoot.activeElement获取,此时this.shadowRoot.activeElement应等于this.selectNode。
  • 若不需要焦点代理,可移除delegatesFocus: true,此时document.activeElement会直接指向Shadow DOM内的.test-select__select元素(前提是元素可聚焦)。

4. 确认焦点触发时机

确保调用#focus()时,组件已经挂载到DOM树中。当前代码中#focus()通过点击事件触发,此时组件已完成挂载,该时机是安全的;若在其他场景(如constructor)中提前调用,会导致焦点设置失败,需调整到connectedCallback之后执行。

内容的提问来源于stack exchange,提问作者tonymx227

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 09:47:52