使用 ShadowDOM 的 WebComponents 简介
WebComponents 可以成为基于组件的 Web 开发的救星。
当所有前端框架都在推行组件化方法和组件化思维时,DOM 却提供了原生的方式来解决这个问题。WebComponents 是实现浏览器原生组件化的整体解决方案。该方案包括:
要快速上手 WebComponents,您只需要 CustomElements V1 polyfill,它提供了一种创建组件和生命周期方法的通用方法,您可以从以下存储库获取:
很多人会说,你需要shadowDOM模板标签和HTML导入语句来创建自定义元素。他们的说法没错,但并不完全正确。你也可以不用这些工具来创建组件。
自定义元素
divCustomElements 是类似于原生 HTML 元素(例如 <div> 、 <span> 等)的元素。span它们是构造函数和其他类似构造函数的扩展,HTMLElement具体取决于您要创建的 CustomElement 类型。
我们来看一个例子;假设你想创建一个 Web 组件,用于快速创建“ figurewith”img和figcaption“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>
此示例取自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>
基本组件代码如下:
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);
它通过 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);
或者直接在 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>
演示:
让我们详细了解一下组件的创建过程:
初始所需部分
所有自定义元素/组件都扩展了基本的 HTMLElement 对象,并具有其属性、样式等功能。
class ImgFigure extends HTMLElement {
connectedCallback() {
// ....
}
}
connectedCallback当它们被附加到 DOM 时,该函数就会执行。所以我们将初始代码放在这个函数中。
最后一部分
最后,我们需要将元素注册到 DOM 中,以便当 DOM 看到该元素时,它会实例化上面提到的类,而不是HTMLElement。
customElements.define('img-figure', ImgFigure);
就这样。这些部分将注册组件,并可通过document.createElementAPI 创建组件。
体验 Web 组件(另一个演示):
但如果你想让它对任何属性变化做出反应呢?
为此,组件类中应该存在两段代码。
一、需要注册可观察属性:
static get observedAttributes() {
return ['attr1', 'attr2'];
}
第二:需要对可观察属性的变化做出反应:
attributeChangedCallback(attr, oldValue, newValue) {
if(oldValue === newValue){
return;
}
if (attr == 'attr1') {
// some stuff
}
if (attr == 'attr2') {
// some other stuff
}
}
让我们来看一下旧组件中的这两段代码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,
});
}
}
这样,您就可以创建自定义元素,而无需过多担心浏览器支持问题。
生命周期方法包括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'});
让我们来看一个 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
closed:指定封闭封装模式。此模式禁止从外部访问封闭影子根的任何节点。
element.shadowRoot === shadowroot; // returns false
element.shadowRoot === null; // returns true
返回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
}
如果所使用的浏览器支持模板标签,则可以按以下方式使用它们:
<template id="img-figure">
<figure>
<img />
<figcaption></figcaption>
</figure>
</template>
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);
更多关于 HTML 模板的信息,请访问:https ://developer.mozilla.org/en-US/docs/Web/HTML/Element/template
HTML导入(已弃用)
HTML Imports 是将 Web 组件传递到所需位置的最简单方法。
它们的工作方式与在文档中导入外部样式表的方式相同。
<link rel="import" href="img-figure.html" />
然后,你的组件文件img-figure.html可以添加其他依赖项,如下所示:
<link rel="stylesheet" href="bootstrap.css">
<script src="jquery.js"></script>
<script src="bootstrap.js"></script>
...
https://www.html5rocks.com/en/tutorials/webcomponents/imports/
帮助
以下资源将有助于您更好地理解 WebComponents 的概念:
- https://developers.google.com/web/fundamentals/web-components/
- https://developer.mozilla.org/en-US/docs/Web/Web_Components/Custom_Elements
- https://developer.mozilla.org/en-US/docs/Web/Web_Components
使用人员/公司WebComponents
为了激发你对 WebComponents 的兴趣:
还有一些人不太善于社交😉
https://github.com/Polymer/polymer/wiki/Who's-using-Polymer?
最后想说的话
WebComponents 非常棒。而且所有浏览器都在逐步实现完全支持。
如果您不确定是否支持 HTML 导入和模板标签,也可以使用常规的 JavaScript 脚本包含方式。
特别鸣谢
非常感谢Alex和Nico的帮助和审阅:
请在评论区告诉我们您对 WebComponents 的看法。
如果您在实现 WebComponents 时遇到任何问题,请在下方留言,我们会尽力提供帮助。
文章来源:https://dev.to/time2hack/introduction-to-webcomponents-with-shadowdom-6fo