首页 > 解决方案 > 将子元素添加到自定义元素

问题描述

我创建了自己的自定义元素,扩展HTLMElement并附加了一个阴影根和样式,如下所示:

class Popover extends HTMLElement {
    mode = "vertical"

    constructor(){
        super();

        let shadow = this.attachShadow({mode: 'open'}),
            style = shadow.appendChild(document.createElement("style"))
    ...

例如,这是一个弹出菜单。我想添加孩子并保持他们的风格。让我们想象一下我的风格是这样的

.Circle {
    width: 100px;
    height: 100px;
    background-color: red;
    border-radius: 50%;
}

我创建一个div元素并应用这样的样式

let circle = document.createElement("div");
circle.className = "Circle";
document.body.appendChild(circle);

我的红色圆圈正确显示。我现在想将该圆圈添加到我的自定义元素中,而不是document.body出现问题。如果我像这样将它附加到我的自定义元素中,它将不会显示(尽管它实际上在 DOM 检查器中):

let p = new Popover();
p.appendChild(circle);
document.body.append(p);

我的圈子没有显示。但是,如果我查看 DOM 检查器,圆形 div 实际上就在那里,并且还应用了 .Circle 属性。

如果我然后尝试将其附加到shadowRoot自定义元素本身,则会显示 div,但所有属性表单.Circle类都消失了(未应用)。

let p = new Popover();
p.shadowRoot.appendChild(circle);
document.body.append(p);

那么,如何向自定义元素添加内容并保留文档类?

标签: javascriptcss

解决方案


好的,我了解到:

  1. 附加 shadow dom 将替换任何元素子元素(它们不会显示),因此必须将子元素附加到 shadow root
  2. Shadow DOM 不会使用文档级样式

所以我决定重写appendChild我的自定义元素的方法,以便附加的元素将被附加,shadowRoot并且它的计算样式被抓取并复制到它(以下方法是我的自定义元素的一部分class

appendChild(element){
    if (!element instanceof HTMLElement)
        throw new TypeError("Expected HTMLElement");

    document.body.appendChild(element); // ensure that style is rendered
    let style = window.getComputedStyle(element);
    element.style.cssText = style.cssText;

    return this.shadowRoot.appendChild(element);
}

编辑:

由于 Firefox 中的一个旧错误,我不得不像这样更改我的代码:

appendChild(element){
    if (!element instanceof HTMLElement)
        throw new TypeError("Expected HTMLElement");

    document.body.appendChild(element); // ensure that style is rendered
    element.style.cssText = this.getComputedStyleCssText(element);

    return this.shadowRoot.appendChild(element);
}

getComputedStyleCssText(element){
    var cssObject = window.getComputedStyle(element),
        prop,
        cssText,
        cssAccumulator = [];

    if(cssObject.cssText != ""){
        return cssObject.cssText;
    }

    for(prop in cssObject){
        if(typeof cssObject[prop] == "string"){
            cssAccumulator.push(prop + " : " + cssObject[prop]);
        }
    }
    return cssAccumulator.join("; ");
}

学分:https ://gist.github.com/johnkpaul/1754808

编辑2: 更好的递归:

appendChild(element){
    if (!element instanceof HTMLElement)
        throw new TypeError("Expected HTMLElement");

    document.body.appendChild(element); // ensure that style is rendered
    
    // do it recursively
    let setStyle = element => {
        element.style.cssText = this.getComputedStyleCssText(element);
        Array.from(element.children).forEach(child => setStyle(child));
    }
    setStyle(element);

    return this.shadowRoot.appendChild(element);
}

getComputedStyleCssText(element){
    var cssObject = window.getComputedStyle(element),
        prop,
        cssText,
        cssAccumulator = [];

    if(cssObject.cssText != ""){
        return cssObject.cssText;
    }

    for(prop in cssObject){
        if(typeof cssObject[prop] == "string"){
            cssAccumulator.push(prop + " : " + cssObject[prop]);
        }
    }
    return cssAccumulator.join("; ");
}

推荐阅读