如何检测元素是否匹配:host CSS选择器?
如何检测元素是否匹配:host CSS选择器?
其实这个问题的核心在于::host是Shadow DOM专属的选择器,它的生效范围仅限于Shadow Root内部的样式上下文,而原生的element.matches()是在全局文档上下文执行的,所以直接调用matches(':host')肯定不会生效。
我之前做自定义组件样式编辑器的时候也踩过这个坑,后来摸索出了一套模拟Shadow内部选择器匹配逻辑的方法,刚好能满足你的需求——不管是纯:host、:host(.active)这类带条件的宿主选择器,还是:host div这种组合选择器,都能正确判断匹配结果。
解决思路
我们需要根据元素所在的上下文(是Shadow Root的宿主元素,还是内部元素),拆解:host相关的选择器,分别验证对应的匹配条件:
- 如果目标元素是Shadow Root的宿主:
- 纯
:host直接返回true; :host(...)就提取括号内的选择器,用原生matches()验证宿主是否符合该条件;- 对于
:host + 组合选择器(比如:host div),宿主元素本身不会匹配这类选择器(因为这类选择器是选Shadow内部的元素),所以返回false。
- 纯
- 如果目标元素是Shadow Root内部的元素:
- 纯
:host直接返回false; :host(...) + 组合选择器需要两步验证:先检查内部元素是否匹配组合部分的选择器,再检查宿主是否符合:host()里的条件;
- 纯
具体实现代码
function matchesInShadow(element, selector) { // 处理:host相关选择器 if (selector.startsWith(':host')) { const root = element.getRootNode(); // 情况1:目标元素是Shadow Root的宿主 if (root instanceof ShadowRoot && root.host === element) { // 纯:host选择器 if (selector === ':host') { return true; } // :host(...) 带条件的宿主选择器 const hostConditionalMatch = selector.match(/^:host\((.*)\)$/); if (hostConditionalMatch) { return element.matches(hostConditionalMatch[1]); } // :host 后跟组合选择器(宿主本身不匹配这类选择器) return false; } // 情况2:目标元素在Shadow Root内部 else if (root instanceof ShadowRoot) { // 纯:host选择器,内部元素不匹配 if (selector === ':host') { return false; } // :host(...) 纯宿主选择器,内部元素不匹配 if (selector.match(/^:host\(.*\)$/)) { return false; } // :host + 组合选择器,比如:host div、:host > .test const hostCombinedMatch = selector.match(/^:host(\(.*\))?\s+(.*)$/); if (hostCombinedMatch) { const hostCondition = hostCombinedMatch[1]; const innerSelector = hostCombinedMatch[2]; // 先检查内部元素是否匹配组合部分的选择器 if (!element.matches(innerSelector)) { return false; } // 再检查宿主是否符合条件 const host = root.host; if (!hostCondition) { // 没有额外条件,只要是当前Shadow的宿主即可 return true; } else { // 提取括号内的条件选择器 const hostSelector = hostCondition.slice(1, -1); return host.matches(hostSelector); } } } } // 非:host选择器,直接用原生matches方法 return element.matches(selector); }
测试你的例子
把你的代码加上这个函数,测试结果如下:
// 注册自定义元素部分不变 class CustomElement extends HTMLElement{ constructor(){ super(); const shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = '<div class="test">Hello, world!</div>'; } } customElements.define('my-element', CustomElement); // 测试代码 const hostEl = document.querySelector('my-element'); const innerEl = hostEl.shadowRoot.querySelector('.test'); console.log(matchesInShadow(innerEl, '.test')); // 输出true,和原生一致 console.log(matchesInShadow(hostEl, ':host')); // 输出true,符合预期 console.log(matchesInShadow(innerEl, ':host .test')); // 输出true,正确匹配组合选择器 // 给宿主加个class,测试带条件的:host选择器 hostEl.classList.add('active'); console.log(matchesInShadow(hostEl, ':host(.active)')); // 输出true
这个方法完全模拟了Shadow Root内部样式的选择逻辑,不管是简单还是复杂的:host相关选择器,都能准确判断匹配结果,刚好适合你做自定义CSS编辑器的场景。
备注:内容来源于stack exchange,提问作者Ood
相关产品推荐
相关产品推荐

