覆盖Web组件中的外部定义样式

时间:2018-04-23 15:56:56

标签: javascript html5 web-component shadow-dom native-web-component

我在不使用任何第三方库(例如Polymer)的情况下将我的第一步迈入web components。其中一个主要卖点是Web组件样式与其他地方定义的样式分离,允许组件的shadow-DOM在类似沙箱的环境中进行样式化。

我遇到的问题是样式如何通过开槽元素级联。由于时隙元素不是影子DOM的一部分,因此只能使用组件模板中的::slotted()选择器对其进行定位。这很好,但它几乎不可能保证Web组件在所有上下文中都能正确显示,因为外部定义的样式也适用于对插槽元素具有不可阻挡的特异性*。

*除了!important

这个问题可以归结为:



customElements.define("my-nav",
  class extends HTMLElement {
    constructor() {
      super();

      const template = document.querySelector("template#my-nav").content;
      this.attachShadow({ mode: "open" })
        .appendChild(template.cloneNode(true));
    }
  }
);

a {
  color: red; /*  >:(  */
}

<template id="my-nav">
  <style>
    .links-container ::slotted(a) {
      color: lime;
      font-weight: bold;
      margin-right: 20px;
    }
  </style>

  <div class="links-container">
    <slot name="links"></slot>
  </div>
</template>

<p>I want these links to be green:</p>
<my-nav>
  <a href="#" slot="links">Link 1</a>
  <a href="#" slot="links">Link 2</a>
  <a href="#" slot="links">Link 3</a>
</my-nav>
&#13;
&#13;
&#13;

我很难理解这个&#34;功能&#34;的价值。我要么必须以其他格式指定我的链接并使用JS创建节点,要么将!important添加到我的颜色属性中 - 仍然不保证一致性字面意思是我还没有定义的任何其他财产。

这个问题是否已在某处得到解决,或者通过改变我的轻量级DOM结构可以轻松解决这个问题?我不知道如何将链接列表添加到插槽中。

2 个答案:

答案 0 :(得分:3)

你是对的,除了每个CSS属性使用!important之外别无解决方案。

相反,我不会使用<slot>并复制您需要的节点:

customElements.define("my-nav",
  class extends HTMLElement {
    constructor() {
      super();

      const template = document.querySelector("template#my-nav").content;
      this.attachShadow({ mode: "open" })
        .appendChild(template.cloneNode(true));
    }
    
    connectedCallback() {
      var links = this.querySelectorAll( 'a[slot]' )
      var container =  this.shadowRoot.querySelector( '.links-container' )
      links.forEach( l => container.appendChild( l ) )
    }
  }
);
a {
  color: red; /*  >:(  */
}
<template id="my-nav">
  <style>
    .links-container > a {
      color: lime;
      font-weight: bold;
      margin-right: 20px;
    }
  </style>

  <div class="links-container">

  </div>
</template>

<p>I want these links to be green:</p>
<my-nav>
  <a href="#" slot="links">Link 1</a>
  <a href="#" slot="links">Link 2</a>
  <a href="#" slot="links">Link 3</a>
</my-nav>

答案 1 :(得分:3)

有意设计<slot>以允许外部代码设置放入其中的内容的样式。如果使用得当,这是一个很棒的功能。

但是,如果您想要更好地控制Web组件中显示的内容,则需要将内容的克隆副本从this.childNodes复制到shadow DOM中。然后你可以100%控制CSS。

行。您实际上只有90%的控制权,因为使用您的组件的人仍然可以设置style属性。

&#13;
&#13;
customElements.define("my-nav",
  class extends HTMLElement {
    constructor() {
      super();

      const template = document.querySelector("template#my-nav").content;
      this.attachShadow({ mode: "open" })
        .appendChild(template.cloneNode(true));
    }
    
    connectedCallback() {
      var container = this.shadowRoot.querySelector('.links-container');
      var children = this.childNodes;
      if (children.length > 0 && container) {
      
        while(container.firstChild) {
          container.removeChild(container.firstChild);
        }
        
        for (var i = 0; i < children.length; i++) {
          container.appendChild(children[i].cloneNode(true));
        }
      }
    }
  }
);
&#13;
a {
  color: red;
}
&#13;
<template id="my-nav">
  <style>
    .links-container a {
      color: lime;
      font-weight: bold;
      margin-right: 20px;
    }
  </style>

  <div class="links-container">
  </div>
</template>

<p>I want these links to be green:</p>
<my-nav>
  <a href="#">Link 1</a>
  <a href="#">Link 2</a>
  <a href="#" style="color: red">Link 3</a>
</my-nav>
&#13;
&#13;
&#13;

正如您在上面的示例中所看到的,第三个链接仍为红色,因为我们设置了style属性。

如果您想阻止这种情况发生,那么您需要从内部内容中删除style属性。

&#13;
&#13;
customElements.define("my-nav",
  class extends HTMLElement {
    constructor() {
      super();

      const template = document.querySelector("template#my-nav").content;
      this.attachShadow({ mode: "open" })
        .appendChild(template.cloneNode(true));
    }
    
    connectedCallback() {
      var container = this.shadowRoot.querySelector('.links-container');
      var children = this.childNodes;
      if (children.length > 0 && container) {
      
        while(container.firstChild) {
          container.removeChild(container.firstChild);
        }
        
        for (var i = 0; i < children.length; i++) {
          container.appendChild(children[i].cloneNode(true));
        }
        
        container.querySelectorAll('[style]').forEach(el => el.removeAttribute('style'));
      }
    }
  }
);
&#13;
a {
  color: red;
}
&#13;
<template id="my-nav">
  <style>
    .links-container a {
      color: lime;
      font-weight: bold;
      margin-right: 20px;
    }
  </style>

  <div class="links-container">
  </div>
</template>

<p>I want these links to be green:</p>
<my-nav>
  <a href="#">Link 1</a>
  <a href="#">Link 2</a>
  <a href="#" style="color: red">Link 3</a>
</my-nav>
&#13;
&#13;
&#13;

我甚至创建了一些组件,允许我读入的独特子项并转换为自定义内部节点。

考虑<video>标记及其<source>个孩子。这些孩子不会真正渲染任何东西,他们只是一种保存数据的方式,用于指示要播放的视频的源位置。

这里的关键是要了解<slot>应该用于什么,并且只使用它,而不是试图强迫它做一些从未打算做的事情。

奖励积分

由于每次将此节点放入DOM时都会调用ConnectedCallback,因此每次都必须小心删除shadow DOM中的任何内容,否则您将反复复制这些节点。

&#13;
&#13;
customElements.define("my-nav",
  class extends HTMLElement {
    constructor() {
      super();

      const template = document.querySelector("template#my-nav").content;
      this.attachShadow({ mode: "open" })
        .appendChild(template.cloneNode(true));
    }
    
    connectedCallback() {
      var container = this.shadowRoot.querySelector('.links-container');
      var children = this.childNodes;
      if (children.length > 0 && container) {
        for (var i = 0; i < children.length; i++) {
          container.appendChild(children[i].cloneNode(true));
        }
      }
    }
  }
);

function reInsert() {
  var el = document.querySelector('my-nav');
  var parent = el.parentNode;
  el.remove();
  parent.appendChild(el);
}

setTimeout(reInsert, 1000);
setTimeout(reInsert, 2000);
&#13;
a {
  color: red;
}
&#13;
<template id="my-nav">
  <style>
    .links-container a {
      color: lime;
      font-weight: bold;
      margin-right: 20px;
    }
  </style>

  <div class="links-container">
  </div>
</template>

<p>I want these links to be green:</p>
<my-nav>
  <a href="#">Link 1</a>
  <a href="#">Link 2</a>
  <a href="#" style="color: red">Link 3</a>
</my-nav>
&#13;
&#13;
&#13;

因此删除重复的节点非常重要:

&#13;
&#13;
customElements.define("my-nav",
  class extends HTMLElement {
    constructor() {
      super();

      const template = document.querySelector("template#my-nav").content;
      this.attachShadow({ mode: "open" })
        .appendChild(template.cloneNode(true));
    }
    
    connectedCallback() {
      var container = this.shadowRoot.querySelector('.links-container');
      var children = this.childNodes;
      if (children.length > 0 && container) {
        while(container.firstChild) {
          container.removeChild(container.firstChild);
        }
        for (var i = 0; i < children.length; i++) {
          container.appendChild(children[i].cloneNode(true));
        }
      }
    }
  }
);

function reInsert() {
  var el = document.querySelector('my-nav');
  var parent = el.parentNode;
  el.remove();
  parent.appendChild(el);
}

setTimeout(reInsert, 1000);
setTimeout(reInsert, 2000);
&#13;
a {
  color: red;
}
&#13;
<template id="my-nav">
  <style>
    .links-container a {
      color: lime;
      font-weight: bold;
      margin-right: 20px;
    }
  </style>

  <div class="links-container">
  </div>
</template>

<p>I want these links to be green:</p>
<my-nav>
  <a href="#">Link 1</a>
  <a href="#">Link 2</a>
  <a href="#" style="color: red">Link 3</a>
</my-nav>
&#13;
&#13;
&#13;