Почему моя тень разрушает мой элемент? - PullRequest
0 голосов
/ 24 марта 2020

Вот демонстрация скрипта пользовательского элемента: https://jsfiddle.net/c4bLo097/5/

Вот код из скрипки:

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

Если вы проверяете <custom-element> с вашими инструментами разработчика, вы должны увидеть, что тень есть. Но мой элемент не будет правильно отображать свою высоту.

Вот примерный пример того, чего я пытаюсь достичь: https://jsfiddle.net/9483s1qb/2/

Спасибо

1 Ответ

2 голосов
/ 25 марта 2020

Когда вы добавляете shadowDOM, содержимое вашего элемента становится "lightDOM" ..

Это больше не отображается в основном DOM и не части вашего shadowDOM.

Как объяснил Supersharp в 2017 году :

Light DOM - это просто простое старое дерево DOM внутри элемента HTML.

Этот термин используется только в контексте веб-компонентов (пользовательских элементов с теневым DOM)
Полагаю, обычный DOM был переопределен как Light в отличие от Shadow .

Спецификации WHATWG называют его теневым root узлом дерева узлов или светлым деревом :

Добавлен Shadow DOM DOM, который восстанавливает, маскирует или заменяет обычный DOM,
, как описано в статье от Google .

Вы должны перенести контент lightDOM в shadowDOM самостоятельно:

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>

Примечания к фрагменту:

  • Шаблон клонирован

  • append в connectedCallback перемещает содержимое,
    , если вы хотите оставить оригинал в lightDOM ( например, использовать его как хранилище данных)
    вы должны клонировать его, как это было сделано с template.

  • slot="title" ходов всего span (включая пролет!) В свой слот в shadowDOM

  • Попробуйте сами:
    На игровой площадке JSFiddle: https://jsfiddle.net/CustomElementsExamples/bzvLcxfe/

    • Что происходит с безымянным : <slot></slot>?

    • Что произойдет, если вы измените <span> на <div>

Будьте в курсе! Браузеры ведут себя по-разному!

Вы можете требовать a setTimeout в connectedCallback, когда (основной) DOM еще не был создан:

    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 детская площадка: https://jsfiddle.net/CustomElementsExamples/bzvLcxfe/

  • в первый желтый Элемент DIV добавлен без a setTimeout

  • в секунду желтый элемент DIV добавляется с a setTimeout

  • outerHTML (известный в connectedCallback) добавляется в оба элемента.

Результат ( Chromium ) Chrome \ Edge \ Opera:

  • lightDOM недоступно доступно,
    setTimeout требуется для задержки кода до завершения события l oop и доступности lightDOM.

Результат в ( Gecko ) FireFox:

  • lightDOM доступно доступно, нет setTimeout требуется

Результат в Safari:

  • Не знаю (пока ), пожалуйста, дайте мне знать

Примечания:

  • Это происходит (в браузерах Chromium), когда customElements.define запускается до ваш DOM готов. Если вы измените JSFiddle и выполните блок <script> после элементов DOM, все в порядке.
    (но большую часть времени вы загружаете библиотеки как можно скорее, чтобы предотвратить FOUCs

  • requestAnimationFrame в connectedCallback имеет то же поведение, что и setTimeout. Подробнее: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop

  • ваш DOM может также будет готов при загрузке ваших элементов asyn c
    <script src=elements.js async ></script>

...