自定义元素 getRootNode.closest() 函数跨越多个(父)shadowDOM 边界
Custom Element getRootNode.closest() function crossing multiple (parent) shadowDOM boundaries
我花了一些时间搜索,但只看到太多常规的“走 DOM”博客或答案只 一个 升级到 getRootnode()
伪代码:
HTML
<element-x>
//# shadow-root
<element-y>
<element-z>
//# shadow-root
let container = this.closest('element-x');
</element-z>
</element-y>
</element-x>
标准 element.closest()
函数不 穿透阴影边界;
所以this.closest('element-x')
returnsnull
因为没有<element-x>
在<element-z>
shadowDom
目标:
从后代 <element z>
(任何嵌套级别)中查找 <element-x>
要求:
一个(递归).closest()
函数,沿着(阴影)DOMs 查找 <element-x>
注意:元素可能有也可能没有阴影DOM(参见<element y>
:只有光DOM)
我明天可以而且会自己做;只是想知道是否有聪明的头脑已经做到了。
资源:
- https://developer.mozilla.org/en-US/docs/Web/API/Node/getRootNode
- https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/host
更新
这是来自以下答案的未压缩代码:
closestElement(selector, base = this) {
function __closestFrom(el) {
if (!el || el === document || el === window) return null;
let found = el.closest(selector);
if (found)
return found;
else
__closestFrom(el.getRootNode().host);
}
return __closestFrom(base);
}
更新#2
我将其更改为我的 BaseElement 上的方法:
closestElement(selector, el = this) {
return (
(el && el != document && el != window && el.closest(selector)) ||
this.closestElement(selector, el.getRootNode().host)
);
}
这与任何 child(影子)DOM
中的 .closest() 相同
但走上 DOM 穿越 shadowroot 边界
针对(极端)缩小进行了优化
//declared as method on a Custom Element:
closestElement(
selector, // selector like in .closest()
base = this, // extra functionality to skip a parent
__Closest = (el, found = el && el.closest(selector)) =>
!el || el === document || el === window
? null // standard .closest() returns null for non-found selectors also
: found
? found // found a selector INside this element
: __Closest(el.getRootNode().host) // recursion!! break out to parent DOM
) {
return __Closest(base);
}
注意:__Closest 函数被声明为 'parameter' 以避免额外的 let
声明......更适合缩小,并保持你的 IDE 来自抱怨
从自定义元素内部调用:
<element-x>
//# shadow-root
<element-y>
<element-z>
//# shadow-root
let container = this.closestElement('element-x');
</element-z>
</element-y>
</element-x>
很好的例子!想要贡献一个有细微差别的 TypeScript 版本——它在向上遍历影子根时遵循 assignedSlot,因此您可以在嵌套的、开槽的自定义元素链中找到最接近的匹配元素。这不是编写 TypeScript 的最奇特的方式,但它完成了工作。
closestElement(selector: string, base: Element = this) {
function __closestFrom(el: Element | Window | Document): Element {
if (!el || el === document || el === window) return null;
if ((el as Slotable).assignedSlot) el = (el as Slotable).assignedSlot;
let found = (el as Element).closest(selector);
return found
? found
: __closestFrom(((el as Element).getRootNode() as ShadowRoot).host);
}
return __closestFrom(base);
}
JS 中的等价物是:
closestElement(selector, base = this) {
function __closestFrom(el) {
if (!el || el === document || el === window)
return null;
if (el.assignedSlot)
el = el.assignedSlot;
let found = el.closest(selector);
return found
? found
: __closestFrom(el.getRootNode().host);
}
return __closestFrom(base);
}
像这样应该可以解决问题
function closestPassShadow(node, selector) {
if (!node) {
return null;
}
if (node instanceof ShadowRoot) {
return this.closestPassShadow(node.host, selector);
}
if (node instanceof HTMLElement) {
if (node.matches(selector)) {
return node;
} else {
return this.closestPassShadow(node.parentNode, selector);
}
}
return this.closestPassShadow(node.parentNode, selector);
}
我花了一些时间搜索,但只看到太多常规的“走 DOM”博客或答案只 一个 升级到 getRootnode()
伪代码:
HTML
<element-x>
//# shadow-root
<element-y>
<element-z>
//# shadow-root
let container = this.closest('element-x');
</element-z>
</element-y>
</element-x>
标准 element.closest()
函数不 穿透阴影边界;
所以this.closest('element-x')
returnsnull
因为没有<element-x>
在<element-z>
shadowDom
目标:
从后代 <element z>
(任何嵌套级别)中查找 <element-x>
要求:
一个(递归).closest()
函数,沿着(阴影)DOMs 查找 <element-x>
注意:元素可能有也可能没有阴影DOM(参见<element y>
:只有光DOM)
我明天可以而且会自己做;只是想知道是否有聪明的头脑已经做到了。
资源:
- https://developer.mozilla.org/en-US/docs/Web/API/Node/getRootNode
- https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/host
更新
这是来自以下答案的未压缩代码:
closestElement(selector, base = this) {
function __closestFrom(el) {
if (!el || el === document || el === window) return null;
let found = el.closest(selector);
if (found)
return found;
else
__closestFrom(el.getRootNode().host);
}
return __closestFrom(base);
}
更新#2
我将其更改为我的 BaseElement 上的方法:
closestElement(selector, el = this) {
return (
(el && el != document && el != window && el.closest(selector)) ||
this.closestElement(selector, el.getRootNode().host)
);
}
这与任何 child(影子)DOM
中的 .closest() 相同但走上 DOM 穿越 shadowroot 边界
针对(极端)缩小进行了优化
//declared as method on a Custom Element:
closestElement(
selector, // selector like in .closest()
base = this, // extra functionality to skip a parent
__Closest = (el, found = el && el.closest(selector)) =>
!el || el === document || el === window
? null // standard .closest() returns null for non-found selectors also
: found
? found // found a selector INside this element
: __Closest(el.getRootNode().host) // recursion!! break out to parent DOM
) {
return __Closest(base);
}
注意:__Closest 函数被声明为 'parameter' 以避免额外的 let
声明......更适合缩小,并保持你的 IDE 来自抱怨
从自定义元素内部调用:
<element-x>
//# shadow-root
<element-y>
<element-z>
//# shadow-root
let container = this.closestElement('element-x');
</element-z>
</element-y>
</element-x>
很好的例子!想要贡献一个有细微差别的 TypeScript 版本——它在向上遍历影子根时遵循 assignedSlot,因此您可以在嵌套的、开槽的自定义元素链中找到最接近的匹配元素。这不是编写 TypeScript 的最奇特的方式,但它完成了工作。
closestElement(selector: string, base: Element = this) {
function __closestFrom(el: Element | Window | Document): Element {
if (!el || el === document || el === window) return null;
if ((el as Slotable).assignedSlot) el = (el as Slotable).assignedSlot;
let found = (el as Element).closest(selector);
return found
? found
: __closestFrom(((el as Element).getRootNode() as ShadowRoot).host);
}
return __closestFrom(base);
}
JS 中的等价物是:
closestElement(selector, base = this) {
function __closestFrom(el) {
if (!el || el === document || el === window)
return null;
if (el.assignedSlot)
el = el.assignedSlot;
let found = el.closest(selector);
return found
? found
: __closestFrom(el.getRootNode().host);
}
return __closestFrom(base);
}
像这样应该可以解决问题
function closestPassShadow(node, selector) {
if (!node) {
return null;
}
if (node instanceof ShadowRoot) {
return this.closestPassShadow(node.host, selector);
}
if (node instanceof HTMLElement) {
if (node.matches(selector)) {
return node;
} else {
return this.closestPassShadow(node.parentNode, selector);
}
}
return this.closestPassShadow(node.parentNode, selector);
}