如何在没有 attachShadow 的情况下创建自定义元素?

How to create a custom element without attachShadow?

假设我有这样的代码:

class MyElem extends HTMLElement {
  constructor() {
    super();
    
    let templateContent = document.getElementById('template-elem').content;
    this.innerHTML = templateContent.cloneNode(true);
  }
}

window.customElements.define('my-elem', MyElem);
<template id="template-elem">
  <div class="a">
    <div class="b">b</div>
    <div class="c">c</div>
  </div>
</template>

<my-elem></my-elem>

为什么这行不通?在 Chrome 检查器中,自定义元素内部没有 HTML。我也尝试过:

this.append(templateContent.cloneNode(true)); 

但这也导致了一个空的 HTML 树。

所有教程都提到使用阴影 DOM,如下所示:

this.attachShadow({mode: 'open'}).appendChild(templateContent.cloneNode(true));

虽然这行得通,但它会强制您对自定义元素使用阴影 DOM。有没有办法只将模板的 HTML 附加到您的自定义元素而不需要使用 Shadow DOM?我更愿意在我的小用例中使用全局 CSS 样式。

你正掉入多个陷阱,就像每个人在他们的第一次组件冒险中一样。

  1. 自定义元素(严格来说只有元素 with shadowDOM 是 Web 组件)具有生命周期阶段和回调。
    此图:https://andyogo.github.io/custom-element-reactions-diagram/必须 才能理解。
    你想在constructor阶段添加DOM内容;但此阶段还没有 DOM 个元素。
    只有在connectedCallback中才能添加DOM内容。
    有了 shadowDOM这是另外一回事了,它的“DocumentFragment”在constructor中可用,可以设置内容,但是不行一个DOM元素呢! connectedCallback 告诉您自定义元素何时附加到 DOM。

  2. 模板内容是一个 DocumentFragment,但您的 .innerHTML 需要一个字符串。
    由于(在你的用法中)<template> 一个 DOM 元素,你可以阅读它的 innerHTML(见下文)


所以,自定义元素没有阴影DOM是可能的:

您将看到 <template> 内容两次,演示了添加内容的两种方式。

<script>
  customElements.define("my-element", class extends HTMLElement {
    connectedCallback() {
      let template = document.getElementById(this.nodeName);
      this.innerHTML = template.innerHTML;
      this.append(template.content.cloneNode(true))
    }
  })

</script>

<template id="MY-ELEMENT">
  Hello, I am an Element!
</template>

<my-element></my-element>


constructor 是您准备 元素的地方

当您执行 document.createElement("my-element") 时,此 constructor 也会 运行。

connectedCallback 运行 当你的元素被添加到 DOM

如果您没有指定方法,则该方法来自其 Class 父级 运行s,因此在上面的代码中,执行来自 HTMLElement 的(默认)constructor
这就是为什么您需要 super() 在您自己的 constructor... 中执行来自 HTMLElement 的 constructor

注:

constructor(){
 let template = document.getElementById("MY-ELEMENT").content.cloneNode(true);
 super().attachShadow({mode:"open").append(template);
}

是完全有效的代码; Google 说 “super needs to 运行 first” 的文档是错误的。
你需要 运行 super() before 你可以 access Elements 自己的范围 this

这就是我喜欢的原因:

constructor(){

 // do anything you want here, but you can not use 'this'

 super() // Sets AND Returns 'this'
   .attachShadow({mode:"open") // both Sets AND Returns this.shadowRoot
   .append(document.getElementById(this.nodeName).content.cloneNode(true));
}

备注append() was not available in IE; so oldskool programmers won't know about its versatility: https://developer.mozilla.org/en-US/docs/Web/API/Element/append

当您的组件冒险将涉及 Class 继承时;
你调用父方法:

connectedCallback(){
  super.connectedCallback()
}

自定义元素的最简单实现是:

class MyComponent extends HTMLElement {
    connectedCallback() {
        this.innerHTML = `<div>Hello world</div>`
    }
}

customElements.define('my-component', MyComponent)
my-component {
  display: block;
  border: 1px dotted #900
}
<my-component></my-component>

但是,如果不使用 Shadow DOM,则无法封装 CSS,而必须通过外部样式 sheet.

用 Shadow DOM编写组件的最简单方法如下所示:

class MyOtherComponent extends HTMLElement {
    constructor() {
        super()
        this.shadow = this.attachShadow({ mode: "open" })
    }

    connectedCallback() {
        this.shadow.innerHTML = `
            <style>
                :host {
                  display: block;
                  border: 1px dotted #900
                }
            </style>
            <div class="component">Hello World!</div>
        `
    }
}

customElements.define('my-other-component', MyOtherComponent)
<my-other-component></my-other-component>

这种方式,你有更多的开销,但组件是真正封装的。