Web 组件中的槽选择器限制

Ken*_*ood 4 web-component shadow-dom native-web-component

slot制作一个可重用的 Web 组件固然很好,但到目前为止它还是有限制的。我面临的是风格问题。即使您知道注入内容的结构是什么,您也无法在组件内定义样式。

详细信息可以从我在 github 上的帖子中找到

我编写了一个组件,并尝试slot从外部注入内容,并尝试将样式添加到组件影子根中的特定内容。 演示

HTML 文件

  <my-navbar>
    <ul>
      <li>link1</li>
      <li>link2</li>
      <li>link3</li>
      </ul>
  </my-navbar>
Run Code Online (Sandbox Code Playgroud)

JS文件

customElements.define('my-navbar', class extends HTMLElement {
  constructor () {
    super();
    const sr = this.attachShadow({ mode: 'open' });
    sr.innerHTML = `
      <style>
      /*worked*/
      ::slotted(ul)
      {
        color:green;
      }
      /*
      Suppose I know the outside content is "ul li", and I directly define the 
      style after they injected into component's slot. However, it just doesn't 
      work because the slotted selector is just a compound selector. It can only 
      affect the first layer 'ul'. It can't affect the child dom 'li' */
      ::slotted(ul li)
      {
        color:red;
      }
      </style>
      <slot></slot>
    `;
  }
});
Run Code Online (Sandbox Code Playgroud)

然而,它不能直接工作,因为你不能使用复杂的选择器::slot(simple_selector) 原因

我找到了一个间接的解决方案,即将外部内容重新附加到组件影子根内部的插槽中。 演示

HTML 文件

  <my-navbar>
    <!--a dom defined a slot property-->
    <ul slot='t'>
      <li>link1</li>
      <li>link2</li>
      <li>link3</li>
      </ul>
    <!--A dom not define slot property-->
    <span>1234</span>
  </my-navbar>
Run Code Online (Sandbox Code Playgroud)

JS文件

customElements.define('my-navbar', class extends HTMLElement {
  constructor () {
    super();
    const sr = this.attachShadow({ mode: 'open' });
    sr.innerHTML = `
      <style>
      ul li
      {
        color:red;
      }
      </style>
      <slot name='t'></slot>
      <slot ></slot>
    `;
    // Do something later...
    setTimeout(this.appendOutsideSlotContentIntoInsideSlot.bind(this), 1000)
  }

    appendOutsideSlotContentIntoInsideSlot()
  {

    // Insert outside dom element which has define slot property into the specify slot inside the shadow root
    debugger;
     for (let objIndex=0;objIndex<this.children.length;) 
     {
        var obj=this.children[objIndex];
        if(obj.slot){
          var slot=this.shadowRoot.querySelector('slot[name='+obj.slot+']');
          if(slot)
          {
              slot.appendChild(obj);
              continue;
           }
       }
       objIndex++;
    }


    // Insert the rest dom which has not define slot property values into the anonymous slot
    var defaultSlot=Array.prototype.slice.call(this.shadowRoot.querySelectorAll('slot')).filter(function(el){ return !el.name})[0];

    debugger;
    if(defaultSlot)
      {
         while (this.children.length>0) 
        {
          defaultSlot.appendChild(this.children[0])

        }

      }
  }
});
Run Code Online (Sandbox Code Playgroud)

嗯,它适用于已定义槽属性的内容,但不适用于没有槽属性的内容。

Int*_*lia 5

除了一些可继承的规则之外,插槽的内容不应直接受到组件的影子 CSS 的影响。它们旨在允许组件外部的 CSS 进行控制。

这是设计使然。

这类似于为 Shadow DOM 中的元素提供的保护,使其不受外部 CSS 的影响。

阅读此处的 分布式节点样式部分: https ://developers.google.com/web/fundamentals/web-components/shadowdom#stylinglightdom

您只能更改插槽内顶级元素的 CSS 规则。甚至你能做的事情也受到限制。所有子元素均由 Shadow DOM 外部的 CSS 控制。

在下面的示例中,您将看到我们可以更改顶级元素或标签的颜色和背景颜色<ul>

customElements.define('my-navbar', class extends HTMLElement {
  constructor () {
    super();
    const sr = this.attachShadow({ mode: 'open' });
    sr.innerHTML = `
      <style>
      ::slotted(ul)
      {
        color: blue;
      }
      
      ::slotted(.bold) {
        font-weight: bold;
        background-color: #222;
        color: #FFF;
      }
      
      ::slotted(.italic) {
        font-style: italic;
        background-color: #AAA;
        color: #000;
      }

      ::slotted(*)
      {
        color: red;
      }
      </style>
      <slot></slot>
    `;
  }
});
Run Code Online (Sandbox Code Playgroud)
<my-navbar>
  <ul class="bold">
    <li>link1</li>
    <li class="italic">link2</li>
    <li>link3</li>
  </ul>
  <ul class="italic">
    <li>link1</li>
    <li class="bold">link2</li>
    <li>link3</li>
  </ul>
</my-navbar>
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,文本为红色而不是蓝色的唯一原因是因为::slotted(*)仅影响两者<ul>,与 和 具有相同的特异性::slotted(ul),并且放置在 后面::slotted(ul)。颜色由标签继承,<li>因为这就是 CSS 的工作原理。

背景颜色仅影响<ul>基于其类别的标签,而不影响<li>具有相同类别的标签。

在下面的示例中,<li>颜色和背景颜色由 Shadow DOM 外部的 CSS 控制。外部规则的行为就好像它们比影子 DOM 规则更具体,即使影子 DOM 规则同时包含 atagclass选择器 ( ul.bold)。

同样,这是设计使然。

customElements.define('my-navbar', class extends HTMLElement {
  constructor () {
    super();
    const sr = this.attachShadow({ mode: 'open' });
    sr.innerHTML = `
      <style>
      ::slotted(ul)
      {
        color: blue;
      }
      
      ::slotted(ul.bold) {
        font-weight: bold;
        background-color: #222;
        color: #FFF;
      }
      
      ::slotted(ul.italic) {
        font-style: italic;
        background-color: #AAA;
        color: #000;
      }

      ::slotted(*)
      {
        color: red;
      }
      </style>
      <slot></slot>
    `;
  }
});
Run Code Online (Sandbox Code Playgroud)
li {
  color: #555;
  backgroung-color: #ddd;
}

.bold {
  font-weight: bold;
  background-color: #FF0;
}

.italic {
  font-style: italic;
  background-color: #0FF;
}
Run Code Online (Sandbox Code Playgroud)
<my-navbar>
  <ul class="bold">
    <li>link1</li>
    <li class="italic">link2</li>
    <li>link3</li>
  </ul>
  <ul class="italic">
    <li>link1</li>
    <li class="bold">link2</li>
    <li>link3</li>
  </ul>
</my-navbar>
Run Code Online (Sandbox Code Playgroud)

<ul>您会注意到和标签的背景颜色<li>是根据bold和的外部类设置的italic

如果您想使用,<slot>您同意使用您的组件的开发人员拥有对放置在插槽中的任何内容的覆盖权。

如果您不希望用户拥有这种控制权,那么阻止它的唯一方法是将组件的子组件移动到组件的影子 DOM 中。

但做的时候要小心。

根据Web 组件构造函数的规则,您不能在构造函数中访问或更改组件的子组件。

但您必须记住,每次将组件插入 DOM 时connectedCallback都会调用。因此,如果开发人员删除然后重新附加您的组件,那么将再次调用该组件。所以你必须添加一个门来防止它被调用两次。connectedCallback

此外,您可能想添加一个MutationObserver来查看用户何时更改组件的子组件。