Shadow DOM Slots, Composition
Shadow DOM Slots, Composition (阴影DOM插槽,构图)
Different types of components like menus, tabs, image galleries, and more, need the content for rendering. (菜单、选项卡、图片库等不同类型的组件需要渲染内容。)
The <custom-tabs> might wait for the actual tab content to be passed like the <select> expects the <option> items.
The code implementing the <custom-menu> is as follows:
<custom-menu>
<title>Book</title>
<item>Javascript</item>
<item>Html</item>
<item>Css</item>
</custom-menu>
Then the component above should render it adequately. (然后,上面的组件应该充分渲染它。)
But it’s essential to know how to perform it. (但知道如何做到这一点至关重要。)
It is possible to analyze the element content and copy-rearrange nodes of the DOM. It is doable, yet while moving the elements to shadow DOM, the CSS styles from the document don’t apply. Hence, you may lose the visual styling. To avoid it, there are <slot> elements supported by shadow Dom. They are automatically fulfilled by the content from the light DOM.
The Named Slots
The Named Slots (已命名插槽)
Here, we are going to demonstrate a simple example that shows how slots operate. (在这里,我们将演示一个简单的示例,展示插槽的运作方式。)
In the example below, two slots filled from the light DOM are provided by shadow DOM:
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
<script>
customElements.define('user-card', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `
<div>Name:
<slot name="username"></slot>
</div>
<div>Id:
<slot name="id"></slot>
</div>
`;
}
});
</script>
<site-card>
<span slot="username">Jack Brown</span>
<span slot="id">1125</span>
</site-card>
</body>
</html>
Within the shadow DOM, <slot name=“X”> specifies the insertion point ( where slot=“X” elements are rendered).
Afterward, composition is implemented by the browser. The composition takes elements from the light DOM rendering them in the matching slots of the shadow DOM. At the end, there is a component, which should be fulfilled with data. (之后,合成由浏览器实现。合成从光DOM中获取元素,在阴影DOM的匹配槽中渲染它们。最后,有一个组件,应该用数据来完成。)
Let’s take a look at the DOM structure after the script without taking into account the composition:
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
<user-card>
#shadow-root
<div>
Name:
<slot name="username"></slot>
</div>
<div>
Id:
<slot name="id"></slot>
</div>
<span slot="username">Jack Brown </span>
<span slot="id">1125</span>
</user-card>
</body>
</html>
As you can see, shadow DOM is created under #shadow-root. So, the element has both shadow and light DOM.
In the shadow DOM, for every <slot name="…"», the browser searches for slot="…" with the same name in the light DOM. It is done for rendering purposes.
The result is known as “flattened” DOM. It will look like this:
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
<user-card>
#shadow-root
<div>
Name:
<slot name="username">
<!-- slot element is inserted into the slot -->
<span slot="username">Jack Brown</span>
</slot>
</div>
<div>
Id:
<slot name="id">
<span slot="id">1125</span>
</slot>
</div>
</user-card>
</body>
</html>
But, note that the “flattened” DOM can only be used for event-handling and rendering. Yet, the document nodes won’t move. (但是,请注意, “扁平化” DOM只能用于事件处理和渲染。但是,文档节点不会移动。)
If you try to run querySelectorAll>, you will see that the nodes are at their places, like here:
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
<user-card>
#shadow-root
<div>
Name:
<slot name="username">
<!-- slot element is inserted into the slot -->
<span slot="username">Jack Brown</span>
</slot>
</div>
<div>
Id:
<slot name="id">
<span slot="id">1125</span>
</slot>
</div>
</user-card>
<script>
// light DOM <span> nodes are still at the same place, under `<user-card>`
alert( document.querySelectorAll('user-card span').length ); // 2
</script>
</body>
</html>
So, the “flattened” DOM is extracted from the shadow DOM by integrating slots. It is rendered and used by the browser for style inheritance and event propagation. However, JavaScript will see it in the before-flattening condition. It is important to note that slot="…" can be valid for the shadow host direct children only. It will be ignored for any nested element. (因此,通过积分槽从阴影DOM中提取“扁平化” DOM。 它由浏览器呈现并用于样式继承和事件传播。 但是, JavaScript将在平坦化之前的条件下看到它。 需要注意的是, slot = “…“仅对影子主机直接子级有效。 它将被任何嵌套元素忽略。)
In the example below, the second span is ignored:
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
<user-card>
<span slot="username">John Brown</span>
<div>
<!-- wrong slot, must be a direct child of user-card -->
<span slot="id">1125</span>
</div>
</user-card>
</body>
</html>
In case there are several elements with the same name inside the light DOM, they can be added into the slot, one by one. (如果在光源DOM中存在多个同名元素,则可以将它们逐个添加到插槽中。)
Let’s see how it can be done:
<user-card>
<span slot="username">Jack</span>
<span slot="username">Brown</span>
</user-card>
And, below find an example of this flattened DOM with two elements within:
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
<slot name="username">
:
<user-card>
#shadow-root
<div>
Name:
<slot name="username">
<span slot="username">Jack</span>
<span slot="username">Brown</span>
</slot>
</div>
<div>
Id:
<slot name="id"></slot>
</div>
</user-card>
</body>
</html>
Fallback Content of the Slot
Fallback Content of the Slot (插槽的后备内容)
After placing something inside a <slot>, it transforms into a fallback. It is demonstrated by the browser when no matching filler is found in the light DOM.
Let’s take a look at an example where Anonymous is rendering the presence of slot=“username” in the light DOM:
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
<div>
Name:
<slot name="username">Anonymous</slot>
</div>
</body>
</html>
The Default Slot
The Default Slot (默认插槽)
The so-called default slot is the first <slot> in the shadow DOM that doesn’t have a name yet. All the nodes from the light DOM that are not slotted anywhere are received by it.
In the example below, the default slot to the <user-card> showing the overall unslotted information about the user, is added:
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
<script>
customElements.define('user-card', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `
<div>Name:
<slot name="username"></slot>
</div>
<div>Id:
<slot name="id"></slot>
</div>
<fieldset>
<legend>Other information</legend>
<slot></slot>
</fieldset>
`;
}
});
</script>
<user-card>
<div>I like to swim.</div>
<span slot="username">Jack Brown</span>
<span slot="birthday">1125</span>
<div>And play piano too.</div>
</user-card>
</body>
</html>
The entire content of the unslotted light DOM is placed in the “Other information” field. (未开槽灯DOM的全部内容都放置在“其他信息”字段中。)
The elements are attached to a slot one by one. So, both of the unslotted information pieces are put together in the default slot. (这些元素逐个连接到一个插槽上。因此,两个未插槽的信息片段都放在默认插槽中。)
Finally, the flattened DOM appears be as follows:
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
<user-card>
#shadow-root
<div>
Name:
<slot name="username">
<span slot="username">Jack Brown</span>
</slot>
</div>
<div>
Id:
<slot name="id">
<span slot="id">1125</span>
</slot>
</div>
<fieldset>
<legend>About me</legend>
<slot>
<div>Hello</div>
<div>I am Jack</div>
</slot>
</fieldset>
</user-card>
</body>
</html>
Menu
Menu (菜单)
Now, let’s get back to the <custom-menu>.
Slots can be practised for distributing elements. (可以练习插槽来分发元素。)
The markup of the <custom-menu> is the following:
<custom-menu>
<span slot="title">Books</span>
<li slot="item">Javascript</li>
<li slot="item">Html</li>
<li slot="item">Css</li>
</custom-menu>
And, the shadow DOM template including the proper slots is shown here:
<template id="tmpId">
<style> /* menu styles */ </style>
<div class="menu">
<slot name="title"></slot>
<ul>
<slot name="item"></slot>
</ul>
</div>
</template>
Let’s describe what happens above:
<span slot=“title”> moves into <slot name=“title”>. Inside the template, you can find multiple <li slot=“item”>. But, there is a single <slot name=“item”>. Therefore, all those <li slot=“item”> are attached to <slot name=“item”> one by one. The list is formed in this way.
The flattened DOM transforms to:
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
<custom-menu>
#shadow-root
<style> /* menu styles */ </style>
<div class="menu">
<slot name="title">
<span slot="title">Books</span>
</slot>
<ul>
<slot name="item">
<li slot="item">Javascript</li>
<li slot="item">Html</li>
<li slot="item">Css</li>
</slot>
</ul>
</div>
</custom-menu>
</body>
</html>
As a rule, <li> must be a direct child of <ul>, inside a valid DOM. However, that is a flattened DOM, describing the way the component is rendered. It is just necessary to insert click handler for opening or closing the list. And the <custom-menu> will be ready:
<!DOCTYPE html>
<html>
<head>
<title>Title of the Document</title>
<style>
ul {
margin: 0;
list-style: none;
padding-left: 20px;
}
::slotted([slot="title"]) {
font-size: 18px;
font-weight: bold;
cursor: pointer;
}
::slotted([slot="title"])::before {
font-size: 14px;
}
.closed::slotted([slot="title"])::before {
content: ;
}
.closed ul {
display: none;
}
</style>
</head>
<body>
<template id="tmpId">
<div class="menu">
<slot name="title"></slot>
<ul>
<slot name="item"></slot>
</ul>
</div>
</template>
<script>
customElements.define('custom-menu', class extends HTMLElement {
connectedCallback() {
this.attachShadow({
mode: 'open'
});
this.shadowRoot.append(tmpId.content.cloneNode(true));
this.shadowRoot.querySelector('slot[name="title"]')
(this.shadowRoot.querySelector ('slot [name = "title"]'))
.onclick = () => {
this.shadowRoot.querySelector('.menu')
(this.shadowRoot.querySelector ('.menu'))
.classList.toggle('closed');
};
}
});
</script>
<custom-menu>
<span slot="title">Books</span>
<li slot="item">Javascript</li>
<li slot="item">Html</li>
<li slot="item">Cup Cake</li>
</custom-menu>
</body>
</html>
The full demo will look as follows:
Books (�?)
Javascript ( Javascript)
Html (HTML)
Css (CSS)
Also, events, methods, and other functionality can be added to it. (此外,还可以向其中添加事件、方法和其他功能。)
Updating
Updating (升级)
The browser supervises the slots and updates rendering in case the slotted elements are added or removed. (如果添加或删除插槽元素,浏览器会监督插槽并更新渲染。)
The changes inside the light DOM nodes become visible immediately because they are not copied but just rendered in slots. So, you needn’t do anything for updating the rendering. But if the code component must get information about the slot changes, then slotchange is accessible. (浅色DOM节点内的更改将立即可见,因为它们不会被复制,而只是在插槽中渲染。 因此,您无需执行任何操作即可更新渲染。 但是,如果代码组件必须获取有关槽更改的信息,则slotchange是可访问的。)
Here is an example:
<!DOCTYPE HTML>
<html>
<head>
<title>Title of the Document</title>
</head>
<body>
<custom-menu id="menu">
<span slot="title">Books</span>
</custom-menu>
<script>
customElements.define('custom-menu', class extends HTMLElement {
connectedCallback() {
this.attachShadow({
mode: 'open'
});
this.shadowRoot.innerHTML = `<div class="menu">
<slot name="title"></slot>
<ul><slot name="item"></slot></ul>
</div>`;
// shadowRoot cannot have event handlers, so use the first child
(//shadowRoot不能具有事件处理程序,因此请使用第一个子项)
this.shadowRoot.firstElementChild.addEventListener('slotchange',
(this.shadowRoot.firstElementChild.addEventListener ('slotchange',)
e => alert("slotchange: " + e.target.name)
);
}
});
setTimeout(() => {
menu.insertAdjacentHTML('beforeEnd', '<li slot="item">Javascript</li>')
}, 1000);
setTimeout(() => {
menu.querySelector('[slot="title"]').innerHTML = "New menu";
}, 2000);
</script>
</body>
</html>
The rendering of the menu is updated every time, without any intervention. (菜单的渲染每次都会更新,无需任何干预。)
Twoslotchange events occur here:
Initialization: during that, slot=“title” occurs at once, as the slot=“title” moves into the matching slot. After a second, slotchange: item happens, once a new <li slot=“item”> is inserted.
Please, take into consideration that there isn’t any slotchange event after two seconds when the slot=“title” content is modified. The reason is that no slot change is there. The content is modified inside the slotted element, that’s a different thing. For tracking internal modifications of the light DOM from JavaScript, there is a possibility of using a more generic mechanism, called MutationObserver. (请注意,修改slot = “title"内容时,两秒钟后没有任何slotchange事件。 原因是没有变更插槽。 内容在开槽元素内部进行修改,这是另一回事。 对于从JavaScript跟踪光DOM的内部修改,有可能使用更通用的机制,称为MutationObserver。)
The Slot API
The Slot API (老虎机API)
Here, we will mention the slot-related methods of JavaScript. As you know, JavaScript considers the real DOM without flattening. But, when the shadow tree includes {mode: ‘open’}, then it can be figured out what elements are assigned to a slot, and in reverse, the slot by the element inside that:
node.assignedSlot: returning the <slot> element, to which the node is assigned.
slot.assignedNodes({flatten: true/false}): the DOM nodes that are assigned to the slot.By default, the flatten option is false. By setting to true, it will look more deeply into the flattened DOM, and return nested slots when it comes to nested components and the fallback content if no node is assigned.
slot.assignedElements({flatten: true/false}): the elements of DOM that are assigned to the slot.
The above-mentioned methods are handy when you wish not only to show the slotted content but track it in JavaScript, too. (当您不仅希望显示插槽内容,而且希望在JavaScript中跟踪时,上述方法非常方便。)
Here is an example:
<!DOCTYPE HTML>
<html>
<head>
<title>Title of the Document</title>
</head>
<body>
<custom-menu id="menu">
<span slot="title">Books</span>
</custom-menu>
<script>
customElements.define('custom-menu', class extends HTMLElement {
connectedCallback() {
this.attachShadow({
mode: 'open'
});
this.shadowRoot.innerHTML = `<div class="menu">
<slot name="title"></slot>
<ul><slot name="item"></slot></ul>
</div>`;
// shadowRoot cannot have event handlers, so use the first child
(//shadowRoot不能具有事件处理程序,因此请使用第一个子项)
this.shadowRoot.firstElementChild.addEventListener('slotchange',
(this.shadowRoot.firstElementChild.addEventListener ('slotchange',)
e => alert("slotchange: " + e.target.name)
);
}
});
setTimeout(() => {
menu.insertAdjacentHTML('beforeEnd', '<li slot="item">Javascript</li>')
}, 1000);
setTimeout(() => {
menu.querySelector('[slot="title"]').innerHTML = "New menu";
}, 2000);
</script>
</body>
</html>
Summary
Summary (概要)
Generally, when an element obtains a shadow DOM, its light DOM is not illustrated. It is possible to find the light DOM elements in particular places of the shadow DOM. (通常,当元素获取阴影DOM时,其浅色DOM未示出。可以在阴影DOM的特定位置找到光DOM元素。)
As a rule, two types of slots are distinguished: named slots and default slots. The process of rendering the slotted elements inside their slots is known as composition. And, the “flattened” DOM is its result.