首页 文章

Web组件中的槽选择器限制

提问于
浏览
2

slot 很适合制作可重复使用的Web组件,但到目前为止它有一个限制 . 我面临的是风格问题 . 你只需要't define the style inside a component, even you know what the inject content'的结构 .

我在github发表的详细信息here

我编写了一个组件,并尝试从外部通过 slot 注入内容,并尝试将样式添加到组件的阴影根中的特定内容 . Demo

HTML文件

<my-navbar>
    <ul>
      <li>link1</li>
      <li>link2</li>
      <li>link3</li>
      </ul>
  </my-navbar>

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

但是,它只是没有't work directly because you just can' _295940用于复杂的选择器 ::slot(simple_selector) Reason

我发现了一个间接解决方案,那个's to re-append the outside content into the slots inside the component'的影子根 . Demo

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>

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])

        }

      }
  }
});

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

1 回答

  • 2

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

    那是设计上的 .

    这类似于对shadow DOM中的元素不受外部CSS影响的保护 .

    阅读 Styling distributed nodes 部分,在此处找到: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>
        `;
      }
    });
    
    <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>
    

    在上面的示例中,文本为红色而不是蓝色的唯一原因是因为 ::slotted(*) 仅影响两个 <ul> ,具有与 ::slotted(ul) 相同的特异性并且位于 ::slotted(ul) 之后 . 颜色由 <li> 标记继承,因为这是CSS的工作方式 .

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

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

    再次,这是设计的 .

    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>
        `;
      }
    });
    
    li {
      color: #555;
      backgroung-color: #ddd;
    }
    
    .bold {
      font-weight: bold;
      background-color: #FF0;
    }
    
    .italic {
      font-style: italic;
      background-color: #0FF;
    }
    
    <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>
    

    您将注意到 <ul><li> 标签的背景颜色是基于 bolditalic 的外部类设置的 .

    如果您想使用 <slot> ,则表示您同意使用您的组件的开发人员对插入插槽的任何内容具有覆盖功能 .

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

    但是当你这样做时要小心 .

    根据Web Component constructors的规则,您无法在构造函数中访问或更改组件的子项 .

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

    此外,您可能需要添加MutationObserver以查看用户何时更改组件的子项 .

相关问题