您在哪里放置 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 模块方法看起来也能做到这一点,因为样式表在此时已经成为一个变量。
<link rel="stylesheet" href="">
有什么问题?摘自文章
所以基本上,你可以,但你必须处理范围问题。这就是他使用
::part
想要表达的意思。“现在我们有了
@import
,这是一个额外的 HTTP 请求,而且众所周知会影响性能。”<link rel="preload" href="/css/{...}" as="style">
可以在这里提供帮助。我更喜欢第一种方法,将 css 注入到模板中的变量。除了我使用 webpack 从单独的文件导入 css 之外。
我经常使用自定义元素,但我故意不使用 shadow dom。我的方法通常是使用 extends 语法(例如,‘
<
ul is=“todo-list”>’),因为它具有更好的浏览器支持,加载速度更快,并且具有更优雅的回退。
然后我使用 Stylable 等工具来处理样式作用域问题,该工具会为您生成唯一的类名,并且当与 Webpack 捆绑在一起时,默认情况下会将这些类名添加到您的主要样式表中,而不会出现命名或作用域问题。