插槽(Slots)

在Shadow DOM里,Slots占着很大的比重。

在Shadow DOM里,Slots占着很大的比重。 - https://developers.google.com/web/fundamentals/getting-started/primers/shadowdom#slots

在创建自定义组件时候,我们希望能够提供进入特定组件唯一的标记,我们想要和组件开发者一样使用/group/style。

组件用户提供的DOM称之为light DOMslots 提供了一种我们能够随意调整标记的方式。根据不同的使用场景来组织这些样式。

插槽有两个方面的内容。

  • Light DOM Elements : 插槽入口
  1. <custom-picture>
  2. <!--Light DOM <img> saying it should be put into the "profile-picture" slot-->
  3. <img src="assets/user.svg" slot="profile-picture">
  4. </custom-picture>
  • The Actual Slot : <slot> 元素, 存在shadow DOM内部,可以通过一个属性名被Light DOM 找到。
  1. <custom-picture>
  2. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  3. #shadow-root
  4. <slot name="profile-picture">
  5. <!--The <img> from the light DOM gets rendered here!-->
  6. </slot>
  7. _________________________________________

划重点啦: Light DOM 和 slots是如何在一起工作的。

如果 引入了该元素,则这些元素可“跨越” shadow DOM 的边界。 这些元素称为分布式节点。从概念上来看,分布式节点似乎有点奇怪。 Slot 实际上并不移动 DOM;它们在 shadow DOM 内部的其他位置进行渲染。 - https://developers.google.com/web/fundamentals/getting-started/primers/shadowdom#slots

如果我不给 <custom-picture> 中的<img> 元素添加‘slot’属性会怎样呢?

没有东西会被渲染出来的,为什么呢?

  1. 一个包含有shadow DOM的宿主元素,仅仅渲染位于 shadow DOM里面的内容
  2. 为了渲染 Light DOM 元素,需要把它变成为 shadow DOM的一部分
  3. 我们把light DOM 元素放进slots是为了让他们成为 shadow DOM的一部分
  4. 在上述例子中,没有出现这样的元素,叫 profile-picture 的插槽
  5. 因为没有这样的元素,所以Light DOM 的<img> 不会被渲染
  6. 被命名的插槽仅容纳那些指定它们要进入特定槽的Light DOM元素

如果我想要渲染所有的元素,但不指明元素应该显示在哪里,怎么做?

这就需要我们在shadow DOM有这样一个通用的没有名字的插槽。例如-

  1. <custom-picture>
  2. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  3. #shadow-root
  4. <!--General purpose slot, render every element from light DOM that doesn't mention a slot name, here.-->
  5. <slot>
  6. <!--The <img> from the light DOM gets rendered here!-->
  7. </slot>
  8. _________________________________________
  9. <!--Light DOM-->
  10. <img src="assets/user.svg">
  11. </custom-picture>

如果我添加了两个未命名插槽呢?

Woah! ?

事实上,我们可以拥有多个未命名插槽,甚至是已命名插槽,但是这些命名基本没什么用处。Llight DOM元素会匹配第一个slot。

  1. <custom-picture>
  2. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  3. #shadow-root
  4. <slot>
  5. <!--The <img> from the light DOM gets rendered here! Winner!-->
  6. </slot>
  7. <slot>
  8. <!--Doesn't come here!-->
  9. </slot>
  10. _________________________________________
  11. <!--Light DOM-->
  12. <img src="assets/user.svg">
  13. </custom-picture>

如果存在一个没有 Light DOM 元素匹配的插槽呢?

不会渲染任何元素的,除非插槽提供一个回退内容,然后这样应该怎么实现呢?

  1. #shadow-root
  2. <slot name="nobody-comes-here">
  3. <h1> I'll show up when noone does!</h1>
  4. </slot>
  5. <style>
  6. /* And that fallback can be styled from within the shadow DOM just like we do styles*/
  7. slot[name="nobody-comes-here"] h1{
  8. color: #bada55;
  9. }
  10. </style>

什么是插槽元件,我们如何给他们添加样式?

这类插槽元素可以使用 ::slotted() 函数性伪类元素设置样式,语法如下。

  1. ::slotted(<compound-selector >) {
  2. /* styles */
  3. }

Example -

  1. <custom-picture>
  2. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  3. #shadow-root
  4. <slot>
  5. <!--The <img> from the light DOM gets rendered here!-->
  6. </slot>
  7. <style>
  8. /* find the slotted image and set their width and height */
  9. ::slotted(img) {
  10. width: 256px;
  11. height: 256px;
  12. }
  13. </style>
  14. _________________________________________
  15. <!--Light DOM-->
  16. <img src="assets/user.svg">
  17. </custom-picture>

这是文档中的相关定义

The ::slotted() pseudo-element represents the elements assigned, after flattening, to a slot. This pseudo-element only exists on slots.

扁平化树结构参考 here.

需要记住的一件重要的事是: 仅仅是宿主元素的第一子元素才可以分配给一个插槽,例如

  1. <custom-picture>
  2. <div class="picture-wrapper">
  3. <!--This won't work! Slots can't pick descendants out of the host element's light DOM tree and put them in.-->
  4. <img src="assets/user.svg" slot="assign-me" />
  5. </div>
  6. </custom-picture>

I, 然而,我不知道(译者注:别的子元素为什么不可以分配给插槽)以及背后的原理是什么。所以我提了一个bug: this bug;

如何向下传递 Light DOM元素呢?

这听起来好像我们不需要考虑这种情况,但是这经常是必要的。

  1. <parent-element>
  2. <!--parent-element uses child-element in it's shadow DOM and we want this span to render inside that child-element's shadow DOM-->
  3. <span slot="parent-slot">Finally</span>
  4. </parent-element>

这是我们创建的自定义元素:

  1. class ParentElement extends HTMLElement {
  2. constructor() {
  3. super();
  4. this.attachShadow({ mode: 'open' });
  5. this.shadowRoot.innerHTML = `
  6. <!--We specify a slot property on the slot itself. Which specifies where it goes in the child-element's shadow DOM-->
  7. <child-element>
  8. <slot name="parent-slot" slot="child-slot"></slot>
  9. </child-element>
  10. `;
  11. }
  12. }
  13. class ChildElement extends HTMLElement {
  14. constructor() {
  15. super();
  16. this.attachShadow({ mode: 'open' });
  17. this.shadowRoot.innerHTML = `
  18. <slot name="child-slot">
  19. <!--The span with the thext "Finally" gets rendered here!-->
  20. </slot>
  21. `;
  22. }
  23. }
  24. window.customElements.define('parent-element', ParentElement);
  25. window.customElements.define('child-element', ChildElement);

插槽也提供了JavaScript API