样式化 Web 组件

发布日期:2026-07-13 10:32:05   来源 : 杭州电子商务研究院    浏览量 :16
杭州电子商务研究院 发布日期:2026-07-13 10:32:05  
16

影子 DOM

文档对象模型 (DOM) 是 HTML 结构、样式和内容的面向对象表示。它将文档公开为树状结构的节点和对象,以便 JavaScript 等编程语言进行操作。Shadow DOM 允许将隐藏的DOM 树添加到文档 DOM 树中。这些隐藏的 DOM 树与父 DOM 树隔离,将 CSS 的范围限制在 Web 组件中,允许在父级中找到重复的类和 ID,而无需交互。这就是实现样式封装和 Web 组件创建的方式。

在 Angular 中使用ShadowDom封装时,应用程序使用浏览器的本机实现。使用Emulated时,它会模拟浏览器的本机功能以克服当前有限的支持,但会应用类似的样式原则和规则。(有关 Angular 中封装类型的更多详细信息,请参阅Angular 中的 CSS 封装

让我们仔细看看它是如何工作的。

使用 Vanilla JavaScript 的自定义组件

假设我们想要创建一个芯片组件,我们可以在代码的任何地方重复使用它,而无需 Angular 的帮助:

目前只有有限数量的浏览器支持影子 DOM,演示中使用了 Chrome v. 68。

HTML

      <html>
  <head>
    <meta charset="utf-8">
    <title>Vanilla JS</title>
    <link rel="stylesheet" type="text/css" href="styles.css">
  </head>
  <body>
    <h1>Vanilla JS</h1>
    <section>
    </section>
    <script src="main.js"></script>
  </body>
</html>
    

CSS

      body {
  font-family: sans-serif;
  font-size: 16px;
  padding: 1rem;
}

section {
  background: ghostwhite;
  border: solid 1px lightgray;
  padding: 1rem;
}
    

我们的页面看起来应该是这样的:

现在创建自定义组件。我们的 JS 文件目前是空白的,所以让我们开始吧。我们需要做的第一件事是定义一个扩展HTMLElement并定义我们的元素的类。这是我们的影子主机,它将包含我们的影子根和影子树。

JS

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

    // Define web component here

  }
}

// customElements.define(name, constructor, options);
customElements.define('custom-chip', CustomChip);
    

请注意,自定义元素名称必须包含连字符。

在我们的 HTML 中:

HTML(部分)

      <section>
  <custom-chip></custom-chip>
</section>
    

我们将在构造函数中定义 Web 组件。我们要做的第一件事是将影子根附加到影子主机。

JS(部分)

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

    //  Create a shadow root
    const shadow = this.attachShadow({ mode: 'open' });

  }
}
    

我们使用open模式,因为它允许我们使用 JavaScript 访问 shadow DOM。如果我们使用closed模式,myCustomElem.shadowRoot将返回null,我们将无法从 shadow DOM 外部访问它。

正如我们在开发工具中看到的,我们现在有一个带有影子 DOM 的新创建组件的实例。

因为我们正在创建一个芯片,所以我们要添加一个标签和一个头像。在构造函数内部,我们创建 HTML 元素,然后将它们附加到影子 DOM。这些元素构成了影子树。

JS(部分)

      //  Create container
const wrapper = document.createElement('div');

//  Add avatar
const avatar = document.createElement('img');
let imgUrl;
if (this.hasAttribute('img')) {
  imgUrl = this.getAttribute('img');
} else {
  imgUrl = 'assets/default.png';
}
avatar.src = imgUrl;

// Add text
const info = document.createElement('span');
info.setAttribute('class', 'info');

// Take attribute content and put it inside the info span
const text = this.getAttribute('label');
info.textContent = text;

  // attach the created elements to the shadow dom
  shadow.appendChild(wrapper);
  wrapper.appendChild(avatar);
  wrapper.appendChild(info);
    

因为我们从标签属性中提取了芯片的文本,所以我们继续将其添加到 HTML 中。图像路径也一样。

HTML(部分)

      <section>
  <custom-chip label="Foo" img="assets/purple.png"></custom-chip>
  <custom-chip label="Bar"></custom-chip>
</section>
    

现在容器内有一个头像和一些文本,让我们来设置它的样式。在构造函数中,我们仍然添加样式,然后将它们附加到 shadow DOM。

JS(部分)

      // Style the component
const style = document.createElement('style');
console.log(style.isConnected);

style.textContent =
  `
  :host {
    background: lightgray;
    border-radius: 40px;
    display: inline-block;
    font-family: sans-serif;
    padding: .5rem 1rem;
  }
  img {
    height: 1rem;
    margin-right: .5rem;
    vertical-align: baseline;
  }
`
// attach styles to shadow DOM
shadow.appendChild(style);
console.log(style.isConnected);
    

我们新创建的组件已定义范围、设置样式并可供使用。

Angular 模拟 DOM

Angular 的工作方式类似。如果我们创建一个CustomChip组件并为其设置相同的样式,我们会得到类似的结果。但在这里 Angular 为我们做了很多繁重的工作。让我们来看看。

我们将使用带有模拟封装的 Angular 6(Angular 的默认封装方法)。

使用 CLI 我们将生成一个应用程序和一个CustomChip组件。

      $ ng new angular
$ cd angular
$ ng generate component CustomChip
    

然后,我们添加所有相同的 CSS 和 HTML,并创建 Typescript 来传递标签和图像源。

应用程序.html

      <h1>Angular</h1>

<!-- Same as the vanilla version -->
<section>
  <custom-chip label="foo" img="/assets/purple.png"></custom-chip>
  <custom-chip label="bar"></custom-chip>
</section>
    

样式.css

      body {
  font-family: sans-serif;
  font-size: 16px;
  padding: 1rem;
}

section {
  background: ghostwhite;
  border: solid 1px lightgray;
  padding: 1rem;
}
    

定制芯片.html

      <div>
  <img [src]="img ? img : 'assets/default.png'">
  <span class="info">{{ label }}</span>
</div>
    

自定义芯片.css

      /* Same as the vanilla version */

:host {
  background: lightgray;
  border-radius: 40px;
  display: inline-block;
  font-family: sans-serif;
  padding: .5rem 1rem;
}
img {
  height: 1rem;
  margin-right: .5rem;
  vertical-align: baseline;
}
    

自定义芯片.ts

      import { Component, Input } from '@angular/core';

@Component({
  selector: 'custom-chip',
  templateUrl: './custom-chip.component.html',
  styleUrls: ['./custom-chip.component.css']
})

export class CustomChipComponent {
  @Input() label;
  @Input() img;
}
    

我们现在以 Angular 方式拥有相同的组件。

造型

为独立组件使用 CSS 的优点在于,如果我们在页面的其他地方添加图像,它不会受到组件中头像样式的影响。让我们添加另一个结构类似的芯片,但这次是纯 HTML。

HTML(部分)

      <section>
  <custom-chip label="foo" img="/assets/purple.png"></custom-chip>
  <custom-chip label="bar"></custom-chip>
</section>

<div>
  <img src="assets/default.png">
  <span>I am also a chip</span>
</div>
    

我们的新元素完全不受组件样式的影响。组件样式仅限于组件本身。

使用styles.css从组件外部设置样式

要在 Angular 中从组件本身外部编辑组件的样式,我们可以将样式包含在项目根目录的style.css文件中。此处放置的样式将全局应用于应用程序,并影响所有元素,甚至组件内部的元素。这是一个很好的解决方案,允许组件继承诸如font-family之类的基本样式。

这与我们的 Vanilla JS 版本不同,在 Vanilla JS 版本中,我们的组件不会继承样式,因为我们的组件与分配给文档 DOM 的样式是隔离的。

style.css (部分)

      img {
  background: palegoldenrod;
  border: solid 1px gray;
  border-radius: 50%;
  display: inline-block;
  margin-top: 12px;
  padding: 2px 10px;
}
div {
  background: yellow;
  border-radius: 0;
  margin-top: 1rem;
  padding: 1rem;
}
    
以上内容来自杭州电子商务研究院推送
关注
关于我们
热门推荐
合作伙伴
免责声明:本站部分资讯来源于网络,如有侵权请及时联系客服,我们将尽快处理
Copyright © 2025-2027 ToB产业网址导航 公安备案 浙公网安备33010602013138号 浙ICP备16025413号-9
支持 反馈 关注 数据