【问题标题】:Overriding externally-defined styles in a web component在 Web 组件中覆盖外部定义的样式
【发布时间】:2018-04-23 15:56:56
【问题描述】:

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

我遇到的问题是样式如何通过开槽元素级联。由于开槽元素不是 shadow 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>

我很难理解这个“功能”的价值。我要么必须以其他格式指定我的链接并使用 JS 创建它们的节点,要么将 !important 添加到我的颜色属性 - 仍然 并不能保证任何其他属性的一致性我还没定义。

这个问题是否已在某处得到解决,或者是否可以通过更改我的轻型 DOM 结构轻松解决?我不确定如何将链接列表放入插槽。

【问题讨论】:

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


    【解决方案1】:

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

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

    好的。你真的只有 90% 的控制权,因为使用你的组件的人仍然可以设置 style 属性。

    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));
            }
          }
        }
      }
    );
    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="#">Link 1</a>
      <a href="#">Link 2</a>
      <a href="#" style="color: red">Link 3</a>
    </my-nav>

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

    如果您想防止这种情况发生,则需要从内部内容中去除 style 属性。

    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'));
          }
        }
      }
    );
    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="#">Link 1</a>
      <a href="#">Link 2</a>
      <a href="#" style="color: red">Link 3</a>
    </my-nav>

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

    想想&lt;video&gt; 标签及其&lt;source&gt; 子标签。这些孩子实际上并没有渲染任何东西,它们只是一种保存数据的方式,用于指示要播放的视频的源位置。

    这里的关键是了解&lt;slot&gt; 应该用于什么,并且只以这种方式使用它,而不是试图强迫它做一些从未打算做的事情。

    奖励积分

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

    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);
    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="#">Link 1</a>
      <a href="#">Link 2</a>
      <a href="#" style="color: red">Link 3</a>
    </my-nav>

    所以删除重复的节点很重要:

    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);
    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="#">Link 1</a>
      <a href="#">Link 2</a>
      <a href="#" style="color: red">Link 3</a>
    </my-nav>

    【讨论】:

    • 哇,这些都是我在直接复制到 shadow DOM 时从未想过的事情,尤其是你关于内联样式的观点。我确实试图将&lt;slot&gt; 用于它不是的东西。非常感谢!
    【解决方案2】:

    你说得对,除了为每个 CSS 属性使用!important 之外,没有其他解决方案。

    相反,我不会使用 &lt;slot&gt; 并复制您需要的节点:

    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>

    【讨论】:

    • 这绝对是完成我所追求的最干净的解决方案 - 谢谢!
    猜你喜欢
    • 2015-03-04
    • 2016-06-03
    • 1970-01-01
    • 1970-01-01
    • 2010-10-20
    • 2019-06-27
    • 2019-10-23
    • 1970-01-01
    • 2020-05-23
    相关资源
    最近更新 更多