在 shadow DOM 内部和外部使用非 shadow DOM 自定义元素

Ste*_*ert 5 html web-component shadow-dom custom-element

我有一个自定义元素(没有 shadow DOM),我希望能够在任何地方使用,甚至在另一个可能使用 shadow 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);
Run Code Online (Sandbox Code Playgroud)
<fancy-button></fancy-button>
Run Code Online (Sandbox Code Playgroud)

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

相反,如果我将样式标记添加为 html 导入文件的一部分,那么样式只能在 shadow 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>
Run Code Online (Sandbox Code Playgroud)

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

Ste*_*ert 5

因此,由于 Supersharp 关于检查我们是否处于影子 DOM 中的建议,我能够找到解决方案。

首先,将样式添加为导入文件的一部分,以便样式默认应用于 Shadow 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);
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

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