思考 Web 组件的样式选项

Avatar of Chris Coyier
Chris Coyier 发布

DigitalOcean 为您的旅程的每个阶段提供云产品。立即开始使用 $200 免费积分!

您在哪里放置 web 组件的样式?

我假设我们在这里使用 Shadow DOM,因为对我来说,这是 web 组件的一大亮点:平台功能,是一种独特而强大的功能,只有平台才能实现。所以这是关于以一种不会泄露的方式为 web 组件定义样式,而不是让全局样式泄露进来(尽管这也很有趣,可以通过 自定义属性 实现,我们将在本文后面讨论)。

如果您在 JavaScript 中构建模板(这很好,因为模板字面量以及我们可以很好地将数据散布到模板中的方式),那么您需要在 JavaScript 中访问这些样式。

const template = `
  <style>${styles}</style>
  <div class="${class}">
    <h2>${title}</h2>
    ${content}
  </div>
`;

这个样式变量从哪里来?也许也是一个模板字面量?

const style = `
  :host {
    background: white;
  }
  h2 {
    font: 900 1.5rem/1.1 -system-ui, sans-serif;
  }
`;

我想这没问题,但这会使一大块混乱的代码直接放在您试图构建此 web 组件的类的某个地方。

另一种方法是 <template> 模板并使其成为 <style> 块的一部分。

<template id="card-template">
  <style>
    :host {
      background: white;
    }
    h2 {
      font: 900 1.5rem/1.1 -system-ui, sans-serif;
    }
  </style>

  <div id="card-hook">
    <h2 id="title-hook"></h2>
    <p id="desc-hook"></p>
  </div>
</template>

我理解这种做法的吸引力,因为它将 HTML 保留在 HTML 中。我不喜欢的是,您必须执行大量手动 shadowRoot.querySelector("#title-hook").innerHTML = myData.title; 工作才能完善该模板。这感觉不像一个方便的模板。我也不喜欢您需要将此模板放在 HTML 中的某个地方。放在哪里?我不知道。随便扔进去吧。扔吧。

CSS 也从 JavaScript 中移除了,但只是从一个尴尬的位置移动到另一个尴尬的位置。

如果我们想将 CSS 保留在 CSS 文件中,我们可以像这样做到

<template id="card-template">
  <style>
    @import "/css/components/card.css";
  </style>

  <div id="card-hook">
    <h2 id="title-hook"></h2>
    <p id="desc-hook"></p>
  </div>
</template>

<link rel="import" type="css" href=""> 的使用似乎已被弃用。)

现在我们有了 @import,这是一个额外的 HTTP 请求,而且众所周知会影响性能。一篇由 Steven Lambert 撰写的 文章 指出它比预期慢了半秒。不理想。我认为用这种方法做应该不会好多少

class MyComponent extends HTMLElement {
    
  constructor() {
    super();
    this.attachShadow({ mode: "open" });

    fetch('/css/components/card.css')
      .then(response => response.text())
      .then(data => {
        let node = document.createElement('style');
        node.innerHTML = data;
        document.body.appendChild(node);
      });
  }

  // ...
}

这看起来像是 Flash-of-Unstyled-Web-Component 吗?我想我应该从我的屁股上下来测试一下。

现在我再次深入研究,::part 似乎已经得到了一些关注(说明)。所以我可以做...

const template = `
  <div part="card">
    <h2>${title}</h2>
    ${content}
  </div>
`;

...然后在全局样式表中编写仅在该 Shadow DOM 内应用的样式,例如

my-card::part(card) {
  background: black;
  color: white;
}

...这有 一点浏览器支持,但可能还不够?

这些“part”选择器只能接触到与其连接的精确元素。您必须通过将部件名称应用于每个 DOM 节点,然后单独为每个节点设置样式,才能完成所有样式设置。这没什么意思,尤其是因为 Shadow DOM 的吸引力在于这种隔离的样式环境,在这个环境中,我们应该能够编写更松散的 CSS 选择器,而不必担心我们的 h2 { } 样式会泄露到其他地方。

看起来如果原生 CSS 模块成为现实,这将是 最有助于的事情

import styles from './styles.css';

class MyElement extends HTMLElement {
  constructor() {
    this.attachShadow({mode: open});
    this.shadowRoot.adoptedStyleSheets = [styles];
  }
}

然而,我不确定这是否是任何性能提升。似乎这与 @import 相比没什么差别。我不得不说,我更喜欢原生 CSS 模块的清晰度和语法。在使用 JavaScript 时编写 JavaScript 感觉很好。

可构造样式表 对于在多个组件之间共享样式表也很有帮助。但 CSS 模块方法看起来也能做到这一点,因为样式表在此时已经成为一个变量。