通过实现选项卡式面板来编写 Web 组件。完成的选项卡如下所示。
Web Component 是浏览器内置的标准,每个主要浏览器都支持此功能。这是一个被低估的功能。
完成一个Web Component需要两个步骤:
- 扩展 HTMLElement 的类。
- 将组件注册为自定义元素。
<!DOCTYPE html>
<html>
<head>
<script>
class WCTab extends HTMLElement { } //Step 1
customElements.define("wc-tab", WCTab) //Step 2
</script>
</head>
</html>
就是这样。Web组件已可供使用。在注册WC时,名称必须始终包含一个连字符,这就是wc-tab而不是wctab的原因。该名称是使用此WC所需的名称。我们可以使用它来创建一个具有相同名称的标签,如下所示。
<body>
<wc-tab></wc-tab>
</body>
在浏览器中打开 html 不会显示任何内容。此时它并不比一个空的 div 好多少。让我们在开始和结束标记之间写一些东西。
<wc-tab>
<p>Hello world!</p>
</wc-tab>
这实际上打印了 Hello world! 在浏览器中!
Shadow Root
您几乎总是应该在 WC 中启用Shadow Root。Shadow Root提供作用域 DOM 树,并将 Web 组件作为其根元素。这使我们能够导入 css 样式,而不会污染全局范围。这意味着我们可以使用 css 样式表,并且这些样式将仅应用于此自定义元素。自定义组件外部具有匹配 css 选择器的任何标记都不受影响。这可以在我们的构造函数中启用,如下所示。
class WCTab extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: "open" });
}
}
一旦进行此更改,浏览器中打印的 hello world 就消失了。当附加 Shadow DOM 时,它会替换我们现有的子元素。WC 的生命周期回调很少,其中之一是connectedCallback. 一旦 WC 附加到 dom,就会调用它。让我们添加它吧!
class WCTab extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: "open" });
}
connectedCallback(){
console.log("connected!");
}
}
connected!当页面刷新时,这会在控制台中打印。
Tab - Example
让我们定义我们的选项卡组件将如何设计。我们的 WC 将每个选项卡作为 div。WC 应定义选项卡及其内容,如下所示。
<wc-tab>
<div name="Tab 1">Tab 1 content</div>
<div name="Tab 2">Tab 2 content</div>
<div name="Tab 3">Tab 3 content</div>
</wc-tab>
我们将读取提供的子项作为输入,并生成一个 UI 将它们显示为选项卡。可以将每个选项卡作为其自己的自定义元素而不是 div 标签。在这个例子中我们将继续使用 div。让我们看看如何访问组件中的子组件。我们将在生命周期方法中执行此操作connectedCallback
connectedCallback(){
let tabs = this.querySelectorAll("div");
console.log(tabs);
}
不幸的是这不起作用。connectedCallback在子级附加到 DOM 之前调用。一旦它们被附加,就没有简单的方法来阅读它们。我们一起去MutationObserver。这会观察子级的变化并调用给定的回调。
connectedCallback() {
let thisNode = this;
let observer = new MutationObserver(function () {
let tabs = thisNode.querySelectorAll("div");
console.log(tabs);
});
// We are only interested in the children of
// this component
observer.observe(this, { childList: true });
}
现在打印出来了NodeList(3) [div, div, div]。这三个 div 是我们需要工作的三个选项卡。让我们添加一个 render 方法来生成 UI。
connectedCallback() {
let thisNode = this;
let observer = new MutationObserver(function () {
thisNode.render();
});
// We are only interested in the children of
// this component
observer.observe(this, { childList: true });
}
render() {
let tabs = this.querySelectorAll("div");
// Generate UI
}
现在我们将渲染逻辑与生命周期方法分离,让我们来编写 UI。
render() {
// Fetch the children as input
let tabs = this.querySelectorAll("div");
// Define basic structure
this.shadowRoot.innerHTML = `
<div class='tab-btn-container'></div>
<div class='tab-panel-container'></div>
`;
let btnContainer = this.shadowRoot.querySelector(".tab-btn-container");
let panelContainer = this.shadowRoot.querySelector(".tab-panel-container");
for (let index = 0; index < tabs.length; index++) {
let currentTab = tabs[index];
this.addTab(currentTab, btnContainer, panelContainer)
}
}
/**
* @param {HTMLElement} tab
* @param {HTMLElement} btnContainer
* @param {HTMLElement} panelContainer
*/
addTab(tab, btnContainer, panelContainer) {
let tabBtn = document.createElement("button");
let clonedTab = tab.cloneNode(true);
let thisNode = this;
let tabName = tab.getAttribute("name");
tabBtn.textContent = tabName;
tabBtn.setAttribute("name", tabName);
btnContainer.appendChild(tabBtn);
panelContainer.appendChild(clonedTab);
}
注意this.shadowRoot用于访问shadow DOM。它适用于所有自定义组件。
接下来,我们实现选择状态。任何时候只有一个选项卡处于活动状态。让我们添加一个方法来将选项卡标记为活动状态。
/**
* @param {String} tabName
*/
activate(tabName) {
// Deactivate previously active tab if any
let activeBtn = this.shadowRoot.querySelector(".tab-btn-container > button.active");
if (activeBtn !== null) {
activeBtn.classList.remove("active");
}
let activeTab = this.shadowRoot.querySelector(".tab-panel-container > div.active");
if (activeTab !== null) {
activeTab.classList.remove("active");
}
// Mark provided tab as active
this.shadowRoot
.querySelector(`.tab-btn-container > button[name='${tabName}']`)
.classList.add("active");
this.shadowRoot
.querySelector(`.tab-panel-container > div[name='${tabName}']`)
.classList.add("active");
}
此方法通过向其中添加活动类来激活选项卡。单击选项卡按钮时必须触发此操作。这是按如下方式完成的。
tabBtn.addEventListener("click", function () {
thisNode.activate(tabName);
})
现在我们与组件进行了交互,让我们设计它的样式。Shadow DOM 没有 head 标签,因此我们可以直接在 ShadowRoot 中附加样式标签或带有样式表的链接标签。
generateStyle() {
let style = document.createElement("style");
style.textContent =
`
*{
background-color: #13005A;
color: white;
font-size: 2rem;
font-family: sans-serif;
}
.tab-panel-container{
padding: 8px;
}
.tab-btn-container{
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.tab-panel-container > div {
display: none;
}
.tab-panel-container > div.active{
display: block;
}
.tab-btn-container{
display: flex;
gap: 8px;
}
.tab-btn-container > button{
background-color: #4e6183;
border: none;
outline: none;
color: white;
padding: 4px 8px;
border-radius: 8px;
cursor: pointer;
}
.tab-btn-container > button.active{
background-color: #03C988;
}
`;
return style;
}
样式的附加方式与任何其他元素相同。
this.shadowRoot.appendChild(this.generateStyle())
就是这样。选项卡组件已准备就绪。这里没有使用一些值得一提的概念,包括自定义属性、模板和插槽。仅使用组件所需的任何内容。