自定义元素getRootNode.closest()函数穿越多个(父级)shadowDOM边界

10

我花了一些时间搜索,但只看到了太多常规的“遍历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')返回null,因为在<element-z>shadowDom中没有<element-x>

目标:

从内部后代<element z>(任何嵌套级别)中找到<element-x>

要求:

一个(递归的).closest()函数,可以遍历(影子)DOM并找到<element-x>

注意:元素可能具有或不具有ShadowDOM(请参见<element y>:仅限lightDOM)

我明天可以自己做,只是想知道是否已经有一些聪明的人已经完成了它。

资源:

更新

以下是来自下面答案的未压缩代码:

        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)
    );
  }

事件

如Intervalia所述,是的,事件是另一种解决方案。
但是...事件需要附加到一个祖先元素... 如何知道要使用哪个祖先元素?


我在想这个问题是否可以通过事件更好地处理。通常情况下,孩子不应该知道关于父母和祖父母的任何事情,这是事件的作用,允许祖先获取有关后代的信息。然后,由于父母创造了孩子,他们会向孩子发送属性或调用函数。我很好奇您试图通过遍历祖先列表来实现什么目标。 - Intervalia
4个回答

7
这与在任何子(影)DOM内使用.closest()相同,但会越过shadowroot边界向上遍历DOM。为了(极度)缩小文件大小进行了优化。
//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>

@Danny,你的建议编辑是错误的。答案中指出:“//在自定义元素上声明为方法:”。箭头函数在这里是不正确的。 - Sebastian Simon

5

很棒的例子!我想提供一个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);
}

在 JavaScript 中的等价代码为:

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);
}

5

像这样的东西应该可以解决问题。

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);

}

易于阅读且功能齐全。 - bertdida

0

只是为了提高可读性/代码风格。这也应该对TypeScript友好。

const closestElement = (selector, target) => {
  const found = target.closest(selector);

  if (found) {
    return found;
  }

  const root = target.getRootNode();

  if (root === document || !(root instanceof ShadowRoot)) {
    return null;
  }

  return closestElement(selector, root.host);
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接