使用链接样式渲染块阴影 DOM

Block shadow DOM rendering with linked styles

Shadow DOM 支持使用 <link> 标签加载样式,其范围与使用 <style> 声明的样式相同,这非常方便,但存在问题样式仅在准备就绪时才应用,并且在加载样式时,FOUC 无法通过自定义元素的 :defined 伪选择器来阻止。我遇到的另一个问题是在构建或连接自定义元素时测量影子根内的元素,因为 "real" 尺寸在加载和应用样式 sheet 后已知,这是我不知道的什么时候会发生(也许 ResizeObserver 会在实施时有所帮助?)
谁能想出一个聪明的方法来解决这些问题(无需手动或通过构建步骤内联样式)?我的担心有道理吗?考虑到 <head> 块渲染中的 <link rel="stylesheet> 并且此功能应该与此类似,是否可以将其视为错误?

如果你想避免 FOUC,你应该隐藏元素直到应用样式。当您使用 fetchXMLHttpRequest<link onload=...>.

时,您可以知道何时加载您的样式

关于 维度问题,它不是 Shadow DOM 特有的,而是 CSS 架构的结果。

总之,经常建议设置元素的with和height,避免FOUC,也避免整页重绘,从而加快渲染速度。

请注意,Shadow DOM 中的 <link rel="stylsheet"> 支持是非常新的,因此可能无法在所有浏览器中按预期工作。

下面是一些创建带有阴影的组件的代码DOM。为简单起见,我使用 setTimeout 模拟 CSS 的较长加载时间。 1 秒后,我将 CSS 应用到元素中。

如您所述,在加载 CSS 之前,元素看起来是单向的。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>FOUC Prevention for WC</title>
    <script>
    var delayedStyles = document.createElement('style');
    delayedStyles.textContent = `
    :host {
      background-color: #DEE;
      border: 1px solid #999;
      display: block;
      width: 400px;
    }

    h1 {
      color: green;
      font: 18px/1em Tahoma;
      padding: 3px 6px;
    }

    p {
      margin: 5px 10px;
      padding: 10px;
    }
    `;
    var template = document.createElement('div');
    template.innerHTML = `
    <h1>The header</h1>
    <p>Some body content</p>
    `;

    // Class for `<my-component>`
    class MyComponent extends HTMLElement {
      constructor() {
        super();

        var sr = this.attachShadow({mode: 'open'});
        setTimeout(() => sr.appendChild(delayedStyles.cloneNode(true)), 1000);


        sr.appendChild(template.cloneNode(true));
      }
    }

    // Define our web component
    customElements.define('my-component', MyComponent);
    </script>
  </head>
  <body>
    <my-component></my-component>
  </body>
</html>

稍作改动,我们就可以隐藏该元素,直到 CSS 加载:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>FOUC Prevention for WC</title>
    <script>
    var delayedStyles = document.createElement('style');
    delayedStyles.textContent = `
    :host {
      background-color: #DEE;
      border: 1px solid #999;
      display: block;
      width: 400px;
    }

    .innerShell {
      display: block !important;
    }

    h1 {
      color: green;
      font: 18px/1em Tahoma;
      padding: 3px 6px;
    }

    p {
      margin: 5px 10px;
      padding: 10px;
    }
    `;
    var template = document.createElement('div');
    template.setAttribute('style', 'display: none;');
    template.className = 'innerShell';
    template.innerHTML = `
    <h1>The header</h1>
    <p>Some body content</p>
    `;

    // Class for `<my-component>`
    class MyComponent extends HTMLElement {
      constructor() {
        super();

        var sr = this.attachShadow({mode: 'open'});
        setTimeout(() => sr.appendChild(delayedStyles.cloneNode(true)), 1000);


        sr.appendChild(template.cloneNode(true));
      }
    }

    // Define our web component
    customElements.define('my-component', MyComponent);
    </script>
  </head>
  <body>
    <my-component></my-component>
  </body>
</html>

为此,我在阴影 DOM 中设置了顶级元素的 style 标签。我设置为display: none; 这样就隐藏了阴影的内部内容DOM.

然后,1 秒后,当加载 CSS 时,它会用 display: block !important 覆盖 display: none;。我必须使用 !important 变得比 style 标签中设置的 css 更具体。

只有在 CSS 加载后我的元素才可见。

作为另一种选择,您还可以在 <link> 标签上放置一个 onload 事件处理程序:

<link rel="stylesheet" href="mystylesheet.css" onload="sheetLoaded()" onerror="sheetError()">

这会让您在代码中知道它已加载,然后只需删除 style 属性。