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

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

我正在迈出进入 web components 的第一步无需使用任何第三方库,例如 Polymer。主要卖点之一是 Web 组件样式与其他地方定义的样式分开,允许组件的影子 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>

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

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

最佳答案

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

但是,如果您想更好地控制 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>

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

想想 <video>标签及其 <source> children 。这些 child 实际上并没有呈现任何东西,它们只是一种保存数据的方式,用于指示要播放的视频的源位置。

这里的关键是理解什么<slot>应该用于并且仅以这种方式使用它,而不是试图强制它做一些它从未打算做的事情。

奖励积分

ConnectedCallback每次将此节点放入 DOM 时都会调用您每次都必须小心删除影子 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>

关于javascript - 在 Web 组件中覆盖外部定义的样式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49985269/

相关文章:

javascript - 如何在多个 Angular 2 项目之间共享一个 Angular 2 组件?

javascript - 如何使用 Twitter Bootstrap 隐藏元素并使用 jQuery 显示它?

javascript - 试图让一个简单的动画重置到它的起始位置,然后重新开始。

javascript onblur() 不工作

html - Bootstrap 列中的垂直中间对齐按钮

iframe - 这个身份验证流程安全吗?

Angular 元素 - ViewEncapsulation

javascript - 如何通过名称调用函数并作为参数传递?

javascript - 断言一个元素是有焦点的

javascript - 将 div 放在 Master Slider 的顶部