<templete>
标签说起。<template>
是一个HTML元素,它允许我们创建一个模板——web组件的HTML结构。模板不必是一大块代码。它可以很简单:
<template>
<p>web component</p>
</template>
<template>
元素很重要,因为它把所有东西都固定在一起。它就像建筑的地基;它是其他一切赖以建立的基础。
<slot>
只是另一个HTML元素,就像<template>
一样。但是在这种情况下,<slot>
定制了<template>
在页面上呈现的内容。
<template>
<p>The <slot>web components</slot> are coming!</p>
</template>
在这里,我们在模板标记中插入了“web components”这个词。如果我们对这个插槽不做任何事情,它默认为标签之间的内容。在这个例子中就是“web components”。
使用<slot>
很像使用占位符。我们可以直接使用占位符,或者定义其他东西来代替。我们使用name属性来实现这一点。
<template>
<p>The <slot name="whats-coming">web components</slot> are coming!</p>
</template>
name
属性告诉web组件哪些内容应该放在模板的哪个位置。现在,我们有一个插槽叫“whats-coming”。
从技术上讲,我们已经完成了组件的“编写”工作,可以把它放到任何我们想要使用它的地方。
<web-component>
<span slot="whats-coming">web app is coming</span>
</web-component>
<template>
<p>The <slot name="whats-coming">web components</slot> are coming!</p>
</template>
看到我们做了什么了吗?我们把<web-component>
组件放在页面上,就像其他的<div>
或其他组件一样。但我们还在这里添加了一个<span>
,它引用了<slot>
的name
属性。当组件渲染时,在<span>
之间的是我们想要换成“web components”的东西。
正如我所说的,你确实需要一些JavaScript来完成这些工作,但它并不是我一直认为的那种超级复杂、有上千行代码、有深度的代码。希望我也能说服你们。
你需要一个注册自定义元素的构造函数。否则,我们的组件就像亡灵一样:它就在那里,但不是完全活着的。
下面是我们将使用的构造函数:
// 用适当的名称<web-component>定义自定义元素
<web-component>
customElements.define("web-component",
// 确保拥有HTML元素内建的所有默认属性和方法
class extends HTMLElement {
// 在创建新的自定义元素时调用
constructor() {
// 调用父构造函数,即' HTMLElement '的构造函数,这样所有的一切都设置得与我们创建内置HTML元素时完全一样
super();
// 获取<模板>并将其存储在' warning '中
let warning = document.getElementById("warningtemplate");
// 将模板的内容存储在' mywarning '中
let mywarning = warning.content;
const shadowRoot = this.attachShadow({mode: "open"}).appendChild(mywarning.cloneNode(true));
}
});
我在那里留下了详细的注释,一行一行地解释事情。除了最后一行:
const shadowRoot = this.attachShadow({mode: "open"}).appendChild(mywarning.cloneNode(true));
我们在这里做了很多事。首先,我们使用自定义元素(this)并创建一个秘密操作——shadow DOM
。mode: open
仅仅意味着:根之外的JavaScript可以访问和操作shadow DOM中的元素,有点像设置对组件的后门访问。
从那里,shadow DOM已经创建,我们将向它添加一个节点。该节点将是模板的深层副本,包括模板的所有元素和文本。模板附加到自定义元素的shadow DOM后,<slot>
和slot属性
将接管内容与它应该去的地方的匹配。
看看这个。现在我们可以弹出同一个组件的两个实例,只需更改一个元素就可以呈现不同的内容。
HTML:
<p>The Apocalypse will never happen!</p>
<apocalyptic-warning>
<span slot="whats-coming">Undead</span>
</apocalyptic-warning>
<apocalyptic-warning>
<span slot="whats-coming">Halitosis Laden Zombie Minions</span>
</apocalyptic-warning>
<template id="warningtemplate">
<style>
p {
background-color: pink;
padding: 0.5em;
border: 1px solid red;
}
</style>
<p>The <slot name="whats-coming">Zombies</slot> are coming!</p>
</template>
js:
customElements.define('apocalyptic-warning',
class extends HTMLElement {
constructor() {
super();
let warning = document.getElementById('warningtemplate');
let mywarning = warning.content;
const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(mywarning.cloneNode(true));
}
});
你可能已经注意到演示中的样式。如你所料,我们完全有能力用CSS来为组件设定样式。事实上,我们可以在<template>
中同时包含<style>
元素。
<template id="warningtemplate">
<style>
p {
background-color: pink;
padding: 0.5em;
border: 1px solid red;
}
</style>
<p>The <slot name="whats-coming">web components</slot> are coming!</p>
</template>
通过这种方式,样式的作用域直接限定在组件上,并且由于shadow DOM,不会泄露给同一页面上的其他元素。
现在,在我的脑海中,我假设一个定制元素获取模板的一个副本,插入您添加的内容,然后使用shadow DOM将其注入到页面中。虽然这是它在前端的样子,但在DOM中却不是这样工作的。自定义元素中的内容保持在它所在的位置,而shadow DOM 有点像覆盖一样被放置在顶部。
而且,由于内容在技术上是在模板之外的,所以我们在模板的<style>
元素中使用的任何后代选择器或类都不会对有插槽的内容产生影响。这并不允许以我希望或期望的方式进行完全封装。但由于自定义元素也是元素,我们可以在任何CSS文件中使用它作为元素选择器,包括页面上使用的主样式表。尽管从技术上讲,插入的材料不在模板中,但它在自定义元素中,CSS中的后代选择器也可以工作。
apocalyptic-warning span {
color: blue;
}
源码:
HTML
<p>The Apocalypse will never happen!</p>
<apocalyptic-warning>
<span slot="whats-coming">Undead</span>
</apocalyptic-warning>
<apocalyptic-warning>
<span slot="whats-coming">Halitosis Laden Zombie Minions</span>
</apocalyptic-warning>
<template id="warningtemplate">
<style>
p {
background-color: pink;
padding: 0.5em;
border: 1px solid red;
}
p span {
color: red;
}
</style>
<p>The <slot name="whats-coming">Zombies</slot> are coming!</p>
</template>
CSS
apocalyptic-warning span {
color: blue;
}
JS
customElements.define('apocalyptic-warning',
class extends HTMLElement {
constructor() {
super();
let warning = document.getElementById('warningtemplate');
let mywarning = warning.content;
const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(mywarning.cloneNode(true));
}
});
让我们来看一个例子,比如一个约会服务的简介,就像你在世界末日后可能需要的一个简介。为了同时样式化默认内容和插入内容,我们需要在<template>
中添加<style>
元素,并在CSS文件中添加样式。
JavaScript代码完全一样,除了我们现在使用的是一个不同的组件名<zombie-profile>
。
JS
customElements.define("zombie-profile",
class extends HTMLElement {
constructor() {
super();
let profile = document.getElementById("zprofiletemplate");
let myprofile = profile.content;
const shadowRoot = this.attachShadow({mode: "open"}).appendChild(myprofile.cloneNode(true));
}
}
);
下面是HTML模板,包括封装的CSS:
<template id="zprofiletemplate">
<style>
img {
width: 100%;
max-width: 300px;
height: auto;
margin: 0 1em 0 0;
}
h2 {
font-size: 3em;
margin: 0 0 0.25em 0;
line-height: 0.8;
}
h3 {
margin: 0.5em 0 0 0;
font-weight: normal;
}
.age, .infection-date {
display: block;
}
span {
line-height: 1.4;
}
.label {
color: #555;
}
li, ul {
display: inline;
padding: 0;
}
li::after {
content: ', ';
}
li:last-child::after {
content: '';
}
li:last-child::before {
content: ' and ';
}
</style>
<div class="profilepic">
<slot name="profile-image"><img src="https://assets.codepen.io/1804713/default.png" alt=""></slot>
</div>
<div class="info">
<h2><slot name="zombie-name" part="zname">Zombie Bob</slot></h2>
<span class="age"><span class="label">Age:</span> <slot name="z-age">37</slot></span>
<span class="infection-date"><span class="label">Infection Date:</span> <slot name="idate">September 12, 2025</slot></span>
<div class="interests">
<span class="label">Interests: </span>
<slot name="z-interests">
<ul>
<li>Long Walks on Beach</li>
<li>brains</li>
<li>defeating humanity</li>
</ul>
</slot>
</div>
<span class="z-statement"><span class="label">Apocalyptic Statement: </span> <slot name="statement">Moooooooan!</slot></span>
</div>
</template>
下面是<zombie-profile>
元素的CSS,以及它在主CSS文件中的后代。注意这里的重复,以确保替换的元素和模板中的元素的样式是相同的。
zombie-profile {
width: calc(50% - 1em);
border: 1px solid red;
padding: 1em;
margin-bottom: 2em;
display: grid;
grid-template-columns: 2fr 4fr;
column-gap: 20px;
}
zombie-profile img {
width: 100%;
max-width: 300px;
height: auto;
margin: 0 1em 0 0;
}
zombie-profile li, zombie-profile ul {
display: inline;
padding: 0;
}
zombie-profile li::after {
content: ', ';
}
zombie-profile li:last-child::after {
content: '';
}
zombie-profile li:last-child::before {
content: ' and ';
}
虽然还有一些问题和其他细微差别,但我希望您现在比几分钟前更有能力使用web组件。就像我们这里一样,先试一下。也许你可以在你的工作中到处添加一个自定义组件,以获得一种感觉,以及它在哪里有意义。