在阴影 DOM 内外使用非阴影 DOM 自定义元素

Using a non-shadow DOM custom element both inside and outside the shadow DOM

我有一个自定义元素(没有阴影 DOM),我希望能够在任何地方使用它,甚至可以在另一个可能使用阴影的自定义元素中使用 DOM。但是,我不确定如何让样式在这两个地方都起作用。

例如,假设我创建了一个简单的 fancy-button 元素:

class fancyButton extends HTMLElement {
  constructor() {
    super();

    this.innerHTML = `
      <style>
      fancy-button button {
        padding: 10px 15px;
        background: rgb(62,118,194);
        color: white;
        border: none;
        border-radius: 4px
      }
      </style>
      <button>Click Me</button>`;
  }
}

customElements.define('fancy-button', fancyButton);
<fancy-button></fancy-button>

在阴影 DOM 元素中,插入的样式标签将允许 fancy-button 样式起作用。但是,如果此组件在阴影 DOM 元素之外使用,则每次使用该元素时都会复制样式标签。

如果我将样式标签添加为 html 导入文件的一部分,则样式仅在阴影之外有效 DOM 但至少它们只声明一次。

<!-- fancy-button.html -->
<style>
fancy-button button {
  padding: 10px 15px;
  background: rgb(62,118,194);
  color: white;
  border: none;
  border-radius: 4px
}
</style>

<script>
class fancyButton extends HTMLElement {
  constructor() {
    super();

    this.innerHTML = `<button>Click Me</button>`;
  }
}

customElements.define('fancy-button', fancyButton);
</script>

添加自定义元素样式以处理在阴影内部和外部使用的最佳方法是什么 DOM?

您可能希望将样式放在单独的 CSS 文件中,该文件与元素的 JS 一起出售。但是正如您所指出的,如果您将元素放在另一个元素的 Shadow DOM 中,那么样式将不会在该范围内工作。出于这个原因,通常最好只创建一个影子根并在其中弹出您的样式。您不想这样做的原因是什么?

所以我找到了解决方案,感谢 Supersharp 关于检查我们是否在阴影中的建议 DOM。

首先,您将样式添加为导入文件的一部分,以便默认情况下样式在阴影外应用 DOM。然后,当元素添加到 DOM 时,我们检查 getRootNode() 以查看它是否已添加到 ShadowRoot 节点。如果有,并且样式还没有注入到根目录中,那么我们可以手动注入样式。

var div = document.createElement('div');
var shadow = div.attachShadow({mode: 'open'});
shadow.innerHTML = '<fancy-button></fancy-button>';

document.body.appendChild(div);
<style data-fs-dialog>
  fancy-button button {
    padding: 10px 15px;
    background: rgb(62,118,194);
    color: white;
    border: none;
    border-radius: 4px
  }
</style>

<script>
class fancyButton extends HTMLElement {
  constructor() {
    super();
  }
  
  connectedCallback() {
    this.innerHTML = `<button>Click Me</button>`;
    
    var root = this.getRootNode();

    // In polyfilled browsers there is no shadow DOM so global styles still style
    // the "fake" shadow DOM. We need to test for truly native support so we know
    // when to inject styles into the shadow dom. The best way I've found to do that
    // is to test the toString output of a shadowroot since `instanceof ShadowRoot`
    // returns true when it's just a document-fragment in polyfilled browsers
    if (root.toString() === '[object ShadowRoot]' && !root.querySelector('style[data-fs-dialog]')) {
      var styles = document.querySelector('style[data-fs-dialog]').cloneNode(true);
      root.appendChild(styles);
    }
  }
}

customElements.define('fancy-button', fancyButton);
</script>

<fancy-button></fancy-button>

当所有浏览器都支持影子<link rel=stylesheet>时DOM,那么内联脚本就可以像robdodson建议的那样变成一个外部样式表,代码会更干净一些。