为什么我的 shadow dom 会破坏我的自定义元素? [英] Why does my shadow dom break my custom element?
问题描述
这是自定义元素的 JSFiddle 演示:) FireFox:
- lightDOM 可用,无
setTimeout
需要
Safari 中的结果:
- 我不知道(还)请告诉我
注意事项:
当
customElements.define
在您的 DOM 准备好之前运行时,会发生这种情况(在 Chromium 浏览器中).如果您更改 JSFiddle 并执行块 在创建 DOM 元素之后,一切都很好.
(但大多数情况下,您会尽快加载库以防止 FOUCsrequestAnimationFrame
与setTimeout
具有相同的行为.更多详情:https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop您的 DOM 可能在您加载元素时异步
也已准备就绪<script src=elements.js 异步 ></script>
connectedCallback
中的 Here is a JSFiddle 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 <custom-element>
with your developer tools, you should see that the shadow is there. But my element will not render its height correctly.
Here is an example JSFiddle of what I'm trying to achieve: https://jsfiddle.net/9483s1qb/2/
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_slotsor with code
customElements.define('my-element', class extends HTMLElement {
constructor() {
//create shadowDOM (thus creating lightDOM) and append Template
super() /* return this */
.attachShadow({mode: 'open'}) /* SETs and RETURNs this.shadowRoot */
.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 thetemplate
.the
slot="title"
reflects the wholespan
(including the span!) to its slot in shadowDOM
See: ::slotted CSS selector for nested children in shadowDOM slotDo 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 theconnectedCallback
) 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 FOUCsrequestAnimationFrame
in theconnectedCallback
has the same behaviour as asetTimeout
. More details: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoopyour DOM might also be ready when you load your elements async
<script src=elements.js async ></script>
这篇关于为什么我的 shadow dom 会破坏我的自定义元素?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!