发布于 2026-01-06 4 阅读
0

使用 ShadowDOM 的 WebComponents 简介

使用 ShadowDOM 的 WebComponents 简介

WebComponents 可以成为基于组件的 Web 开发的救星。

当所有前端框架都在推行组件化方法和组件化思维时,DOM 却提供了原生的方式来解决这个问题。WebComponents 是实现浏览器原生组件化的整体解决方案。该方案包括:

要快速上手 WebComponents,您只需要 CustomElements V1 polyfill,它提供了一种创建组件和生命周期方法的通用方法,您可以从以下存储库获取:

GitHub 标志 Web组件/ polyfill

Web 组件 Polyfills

很多人会说,你需要shadowDOM模板标签和HTML导入语句来创建自定义元素。他们的说法没错,但并不完全正确。你也可以不用这些工具来创建组件。

自定义元素

divCustomElements 是类似于原生 HTML 元素(例如 <div> 、 <span> 等)的元素。span它们是构造函数和其他类似构造函数的扩展,HTMLElement具体取决于您要创建的 CustomElement 类型。

我们来看一个例子;假设你想创建一个 Web 组件,用于快速创建“ figurewith”imgfigcaption“together”的组合。通常,HTML 代码如下所示:

<figure>
  <img
     src="https://developer.cdn.mozilla.net/media/img/mdn-logo-sm.png"
     alt="An awesome picture">
  <figcaption>MDN Logo</figcaption>
</figure>
Enter fullscreen mode Exit fullscreen mode

此示例取自https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure

该组件看起来会是这样的:

<img-figure
  caption="MDN Logo"
  alt="An awesome picture"
  src="https://developer.cdn.mozilla.net/media/img/mdn-logo-sm.png"
></img-figure>
Enter fullscreen mode Exit fullscreen mode

基本组件代码如下:

class ImgFigure extends HTMLElement {
  connectedCallback() {
    this.src = this.getAttribute("src") || null;
    this.caption = this.getAttribute("caption") || "";
    this.alt = this.getAttribute("alt") || null;
    this.render();
  }

  render() {
    this.innerHTML = this.template({
      src: this.src,
      alt: this.alt,
      caption: this.caption
    });
  }

  template(state) { 
    return `
    <figure>
      <img
        src="${state.src}"
        alt="${state.alt || state.caption}">
      <figcaption>${state.caption}</figcaption>
    </figure>
    `;
  }
}

customElements.define('img-figure', ImgFigure);
Enter fullscreen mode Exit fullscreen mode

它通过 JavaScript 的用法如下:

// create element
const i = document.createElement('img-figure');

//set the required attributes
i.setAttribute('src', '//res.cloudinary.com/time2hack/image/upload/goodbye-xmlhttprequest-ajax-with-fetch-api-demo.png');
i.setAttribute('caption', 'GoodBye XMLHttpRequest; AJAX with fetch API (with Demo)');
i.setAttribute('alt', 'GoodBye XMLHttpRequest');

//attach to the DOM
document.body.insertBefore(i, document.body.firstElementChild);
Enter fullscreen mode Exit fullscreen mode

或者直接在 DOM 中创建元素,如下所示:

<img-figure 
  style="max-width: 400px"
  src="//res.cloudinary.com/time2hack/image/upload/ways-to-host-single-page-application-spa-static-site-for-free.png"
  alt="Free Static Hosting"
  caption="Ways to host single page application (SPA) and Static Site for FREE">
</img-figure>
Enter fullscreen mode Exit fullscreen mode

演示:


让我们详细了解一下组件的创建过程:

初始所需部分

所有自定义元素/组件都扩展了基本的 HTMLElement 对象,并具有其属性、样式等功能。

class ImgFigure extends HTMLElement {
  connectedCallback() {
    // ....
  }
}
Enter fullscreen mode Exit fullscreen mode

connectedCallback当它们被附加到 DOM 时,该函数就会执行。所以我们将初始代码放在这个函数中。

最后一部分

最后,我们需要将元素注册到 DOM 中,以便当 DOM 看到该元素时,它会实例化上面提到的类,而不是HTMLElement

customElements.define('img-figure', ImgFigure);
Enter fullscreen mode Exit fullscreen mode

就这样。这些部分将注册组件,并可通过document.createElementAPI 创建组件。

体验 Web 组件(另一个演示):


但如果你想让它对任何属性变化做出反应呢?

为此,组件类中应该存在两段代码。

一、需要注册可观察属性:

static get observedAttributes() {
  return ['attr1', 'attr2'];
}
Enter fullscreen mode Exit fullscreen mode

第二:需要对可观察属性的变化做出反应:

attributeChangedCallback(attr, oldValue, newValue) {
  if(oldValue === newValue){
    return;
  }
  if (attr == 'attr1') {
    // some stuff
  }
  if (attr == 'attr2') {
    // some other stuff
  }
}
Enter fullscreen mode Exit fullscreen mode

让我们来看一下旧组件中的这两段代码img-frame

class ImgFigure extends HTMLElement {
  connectedCallback() {
    this.src = this.getAttribute('src') || null;
    this.caption = this.getAttribute('caption') || '';
    this.alt = this.getAttribute('alt') || null;
    this.render();
  }
  static get observedAttributes() {
    return ['src'];
  }

  attributeChangedCallback(attr, oldValue, newValue) {
    if(oldValue === newValue){
      return;
    }
    if (attr === 'src') {
      this.querySelector('img').src = newValue;
    }
  }
  render() {
    this.innerHTML = template({
      src: this.src,
      alt: this.alt,
      caption: this.caption,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

这样,您就可以创建自定义元素,而无需过多担心浏览器支持问题。

生命周期方法包括customElement

方法 用法/说明
构造函数() 当元素创建或升级时调用
connectedCallback() 当元素插入文档中时调用,包括插入到影子树中。
disconnectedCallback() 当元素从文档中移除时调用。
attributeChangedCallback(attrName, oldVal, newVal, namespace) 当元素的属性发生更改、添加、删除或替换时调用(仅对可观察属性调用)
adoptCallback(oldDocument, newDocument) 当元素被纳入新文档时调用

支持?

我可以启用 custom-elementsv1 吗?以下是 caniuse.com 提供的关于主流浏览器对 custom-elementsv1 功能支持情况的数据。

等等!Firefox 正好可以提供支持customElements

摘要:

这是一份事后通知,告知大家我们正在着手实现自定义元素(包括独立的自定义元素和自定义内置元素),并且之前从未对外公开的旧规范实现将被移除。我们已接近完成实现,但在正式发布该功能之前,仍需进行一些性能优化工作。我们计划首先在 Nightly 版本中启用该功能,以便收集更多用户反馈。(正式发布前会发布发布意向书)

https://groups.google.com/forum/#!msg/mozilla.dev.platform/BI3I0U7TDw0/6-W39tXpBAAJ


关于自定义元素的详细阅读:https://developers.google.com/web/fundamentals/web-components/customelements

那么ShadowDOM呢?

暗影领域

ShadowDOM 是一种将底层 DOM 和 CSS 封装在 Web 组件中的方法。因此,如果您确实需要这种封装,例如当您向第三方提供组件时,请使用 ShadowDOM。

首先,你可以将 ShadowDOM 附加到attachShadow组件上,然后对其执行操作:

element.attachShadow({mode: 'open'});
Enter fullscreen mode Exit fullscreen mode

让我们来看一个 ShadowDOM 的例子:

attachShadow方法需要一个配置对象,该对象仅用于描述封装情况。该对象将有一个键mode,其值为 `true`open或 `false` closed

https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow中所述:

mode:一个字符串,用于指定影子 DOM 树的封装模式。取值范围为:

element.shadowRoot === shadowroot; // returns true
Enter fullscreen mode Exit fullscreen mode

closed:指定封闭封装模式。此模式禁止从外部访问封闭影子根的任何节点。

element.shadowRoot === shadowroot; // returns false
element.shadowRoot === null; // returns true
Enter fullscreen mode Exit fullscreen mode

返回attachShadow一个ShadowRoot可以像普通文档一样使用并对其执行操作的对象。

支持?

我可以启用 shadowdomv1 吗?以下是 caniuse.com 提供的关于主流浏览器对 shadowdomv1 功能支持情况的数据。

关于 ShadowDOM 的更多详细信息,请访问:https://developers.google.com/web/fundamentals/web-components/shadowdom


HTML模板

HTML模板提供了一种机制,可以将页面上的标记发送出去,但这些标记不会被渲染。如果您希望尽可能减小JavaScript包的大小,这将非常有用。

模板添加到文档后,可以对其进行克隆,然后使用 JavaScript 填充相关的动态内容。

它的支持范围仍然不够广;你可以用以下代码来验证这一点。

if ('content' in document.createElement('template')) {
  // operate on the template
}
Enter fullscreen mode Exit fullscreen mode

如果所使用的浏览器支持模板标签,则可以按以下方式使用它们:

<template id="img-figure">
  <figure>
    <img />
    <figcaption></figcaption>
  </figure>
</template>
Enter fullscreen mode Exit fullscreen mode
let template = () => `Template tag not supported`;
const t = document.querySelector('#img-figure');
if ('content' in document.createElement('template')) {
  template = (state) => {
    const img = t.content.querySelector('img');
    const caption = t.content.querySelector('figcaption');
    img.setAttribute('src', state.src);
    img.setAttribute('alt', state.alt || state.caption);
    caption.innerHTML = state.caption;
    return document.importNode(t.content, true);
  }
} else {
  template = (state) => { //fallback case
    const d = document.createElement('div');
    d.innerHTML = t.innerHTML;
    const img = d.querySelector('img');
    const caption = d.querySelector('figcaption');
    img.setAttribute('src', state.src);
    img.setAttribute('alt', state.alt || state.caption);
    caption.innerHTML = state.caption;
    return d.firstElementChild;
  }
}

class ImgFigure extends HTMLElement {
  connectedCallback() {
    this.src = this.getAttribute("src") || null;
    this.caption = this.getAttribute("caption") || "";
    this.alt = this.getAttribute("alt") || null;
    this.render();
  }

  render() {
    this.appendChild(template({
      src: this.src,
      alt: this.alt,
      caption: this.caption,
    }));
  }
}

customElements.define('img-figure', ImgFigure);
Enter fullscreen mode Exit fullscreen mode

更多关于 HTML 模板的信息,请访问:https ://developer.mozilla.org/en-US/docs/Web/HTML/Element/template


HTML导入(已弃用)

HTML Imports 是将 Web 组件传递到所需位置的最简单方法。

它们的工作方式与在文档中导入外部样式表的方式相同。

<link rel="import" href="img-figure.html" />
Enter fullscreen mode Exit fullscreen mode

然后,你的组件文件img-figure.html可以添加其他依赖项,如下所示:

<link rel="stylesheet" href="bootstrap.css">
<script src="jquery.js"></script>
<script src="bootstrap.js"></script>
...
Enter fullscreen mode Exit fullscreen mode

https://www.html5rocks.com/en/tutorials/webcomponents/imports/


帮助

以下资源将有助于您更好地理解 WebComponents 的概念:


使用人员/公司WebComponents

为了激发你对 WebComponents 的兴趣:

还有一些人不太善于社交😉

https://github.com/Polymer/polymer/wiki/Who's-using-Polymer?


最后想说的话

WebComponents 非常棒。而且所有浏览器都在逐步实现完全支持。

如果您不确定是否支持 HTML 导入和模板标签,也可以使用常规的 JavaScript 脚本包含方式。


特别鸣谢

非常感谢AlexNico的帮助和审阅:

[ @nogizhopaboroda ](https://github.com/nogizhopaboroda) | [ @nvignola ](https://github.com/nvignola)

请在评论区告诉我们您对 WebComponents 的看法。

如果您在实现 WebComponents 时遇到任何问题,请在下方留言,我们会尽力提供帮助。


文章来源:https://dev.to/time2hack/introduction-to-webcomponents-with-shadowdom-6fo