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
相关产品推荐
相关产品推荐

