Why does my shadow dom break my custom element?

扶醉桌前 提交于 2020-04-17 22:55:12

问题


Here is a fiddle demonstration of the custom element: https://jsfiddle.net/c4bLo097/5/

Here is the code from the fiddle:

JavaScript:
window.customElements.define('test-element', class TestElement extends HTMLElement {
    constructor() {
    super()

    let contents = `
      <style>
        :host {
          display: block;
        }
        :host([hidden]) {
          display: none;
        }
      </style>`

    // convert string to nodes
    let template = document.createElement('template')
    template.innerHTML = contents

    // create shadow
    this.attachShadow({mode: 'open'})

    // insert nodes
    this.shadowRoot.appendChild(template.content.cloneNode(true))
  }
})


HTML:
<test-element>
  This element should have a natural height.

  <div style="height: 300px;"></div>

  I should be able to see this text on a green background.
</test-element>


CSS:
test-element {
  width: 200px;
  background: green;
}

If you inspect the <custom-element> with your devloper tools, you should see that the shadow is there. But my element will not render its height correctly.

Here is an example fiddle of what I'm trying to achieve: https://jsfiddle.net/9483s1qb/2/

Thank you


回答1:


When you add shadowDOM the content of your element becomes "lightDOM"..

It is no longer displayed in the main DOM and not part of your shadowDOM.

As Supersharp explained in 2017:

The Light DOM is simply the plain old DOM tree inside a HTML element.

The term is only used in context of Web Components (Custom Elements WITH Shadow DOM)
I suppose the normal DOM was redefined as Light in contrast with Shadow.

The WHATWG specs call it the shadowroot host's node tree, or light tree:

The Shadow DOM is the added DOM that recovers, masks, or replaces the normal DOM,
as explained in the article from Google.

You have to transfer lightDOM content to shadowDOM yourself:

  • with SLOTs:
    https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots

  • or with code

customElements.define('my-element', class extends HTMLElement {
    constructor() {
    super()
    //create shadowDOM (thus creating lightDOM) and append Template
    this.attachShadow({mode: 'open'})
        .append(document.getElementById(this.nodeName).content.cloneNode(true))
  }
  connectedCallback(){
    //append content from lightDOM
    this.shadowRoot.append(...this.querySelectorAll('DIV'));
  }
})
my-element{
  border: 1px dashed blue;
}
<template id="MY-ELEMENT">
  <style>
    :host {
      display: block;
      font-size:20px;
    }
    h4{
      background:yellow;
      margin: .5em 0;
    }
    div{
      background:lightcoral;
    }
  </style>
  <h4><slot name="title"></slot></h4>
</template>

<my-element>
  <!-- begin lightDOM because my-element has shadowDOM -->
  <span slot="title">whole SPAN is slotted</span>
  <div>I am appended</div>
  <div>appended too</div>
  <p>I remain (invisible) in lightDOM</p>
  <!-- end lightDOM -->
</my-element>

<my-element>
  <!-- begin lightDOM because my-element has shadowDOM -->
  <span slot="title">slotted too</span>
  <div>appended again</div>
  I remain (invisible) in lightDOM
  <!-- end lightDOM -->
</my-element>

Snippet Notes:

  • The template is cloned

  • the append in the connectedCallback moves the content,
    if you want to leave the original in lightDOM (eg. use it as a datastore)
    you have to clone it like done with the template.

  • the slot="title" moves the whole span(including the span!) to its slot in shadowDOM

  • Do try yourself:
    In the JSFiddle playground: https://jsfiddle.net/CustomElementsExamples/bzvLcxfe/

    • What happens with an unnamed: <slot></slot>?

    • What happens if you change the <span> to a <div>

Be Aware!! Browsers behave different!

You might require a setTimeout in the connectedCallback when the (main) DOM has not been instantiated yet:

    connectedCallback() {
      let savedHTML = this.outerHTML;
      //append content from lightDOM
      const append = (selector) =>
        this.shadowRoot.append(...this.querySelectorAll(selector), savedHTML);

      if (this.outerHTML.includes("timeout"))
        setTimeout(() => append('DIV'))
      else
        append('DIV');
    }

JSFiddle playground: https://jsfiddle.net/CustomElementsExamples/bzvLcxfe/

  • in the first yellow Element DIVs are appended without a setTimeout

  • in the second yellow Element DIVs are appended with a setTimeout

  • the outerHTML (known in the connectedCallback) is appended in both Elements.

Result in (Chromium) Chrome\Edge\Opera:

  • lightDOM is not available,
    setTimeout required to delay code till Event loop has finished, and lightDOM is available.

Result in (Gecko) FireFox:

  • lightDOM is available, no setTimeout required

Result in Safari:

  • I don't know (yet) please let me know

Notes:

  • This happens (in Chromium Browsers) when the customElements.define is run before your DOM is ready. If you change the JSFiddle and execute the <script> block after the DOM elements are created, all is fine.
    (but most of the time you load libraries ASAP to prevent FOUCs

  • requestAnimationFrame in the connectedCallback has the same behaviour as a setTimeout. More details: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop

  • your DOM might also be ready when you load your elements async
    <script src=elements.js async ></script>



来源:https://stackoverflow.com/questions/60838990/why-does-my-shadow-dom-break-my-custom-element

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!