在现代 web 开发中,抽屉 / 侧边栏组件已经成为了一个常见的 UI 元素。这种组件可以提供额外的内容或功能,同时不会占用页面的主要空间。在 Web Components 中,我们可以利用 Shadow DOM 和 Custom Elements 来实现一个可复用的抽屉 / 侧边栏组件。
1. 设计组件结构
在设计组件结构时,我们需要考虑以下几个方面:
- 抽屉 / 侧边栏的位置和大小
- 抽屉 / 侧边栏的打开 / 关闭状态
- 抽屉 / 侧边栏内部的内容
根据这些考虑,我们可以设计出一个基本的组件结构:
<my-drawer> <div class="drawer-content"> <!-- 抽屉 / 侧边栏的内容 --> </div> <div class="drawer-overlay"></div> </my-drawer>
这个结构中,my-drawer
是一个自定义元素,它包含了两个子元素:.drawer-content
和 .drawer-overlay
。.drawer-content
用于放置抽屉 / 侧边栏的内容,.drawer-overlay
用于遮盖页面内容,使得抽屉 / 侧边栏打开时,页面内容不可操作。
2. 实现组件行为
在组件结构设计好之后,我们需要实现组件的行为。具体来说,我们需要实现以下几个功能:
- 打开 / 关闭抽屉 / 侧边栏
- 点击
.drawer-overlay
时关闭抽屉 / 侧边栏 - 在页面上使用多个抽屉 / 侧边栏时,确保它们相互独立
2.1 打开 / 关闭抽屉 / 侧边栏
为了实现打开 / 关闭抽屉 / 侧边栏的功能,我们可以在 my-drawer
元素中添加一个 opened
属性,用于表示抽屉 / 侧边栏的状态。当 opened
属性为 true
时,抽屉 / 侧边栏打开;当 opened
属性为 false
时,抽屉 / 侧边栏关闭。
<my-drawer opened> <div class="drawer-content"> <!-- 抽屉 / 侧边栏的内容 --> </div> <div class="drawer-overlay"></div> </my-drawer>
为了响应 opened
属性的变化,我们可以使用 attributeChangedCallback
方法。当 opened
属性发生变化时,我们可以在这个方法中更新抽屉 / 侧边栏的状态。
// javascriptcn.com 代码示例 class MyDrawer extends HTMLElement { constructor() { super(); // 初始化抽屉 / 侧边栏的状态 this._opened = false; } // 监听 opened 属性的变化 static get observedAttributes() { return ['opened']; } // 当 opened 属性发生变化时,更新抽屉 / 侧边栏的状态 attributeChangedCallback(name, oldValue, newValue) { if (name === 'opened') { this._opened = newValue !== null; this._update(); } } // 打开抽屉 / 侧边栏 open() { this._opened = true; this.setAttribute('opened', ''); this._update(); } // 关闭抽屉 / 侧边栏 close() { this._opened = false; this.removeAttribute('opened'); this._update(); } // 更新抽屉 / 侧边栏的状态 _update() { // ... } }
2.2 点击 .drawer-overlay
时关闭抽屉 / 侧边栏
当抽屉 / 侧边栏打开时,我们需要在 .drawer-overlay
上添加一个点击事件,用于关闭抽屉 / 侧边栏。为了确保 .drawer-overlay
能够接收点击事件,我们需要在 my-drawer
元素的 Shadow DOM 中添加一个 .drawer-overlay
元素。
// javascriptcn.com 代码示例 class MyDrawer extends HTMLElement { constructor() { super(); // 创建 Shadow DOM const shadowRoot = this.attachShadow({ mode: 'open' }); // 创建抽屉 / 侧边栏的内容 const content = document.createElement('div'); content.classList.add('drawer-content'); content.innerHTML = this.innerHTML; // 创建遮盖层 const overlay = document.createElement('div'); overlay.classList.add('drawer-overlay'); // 将抽屉 / 侧边栏的内容和遮盖层添加到 Shadow DOM 中 shadowRoot.appendChild(content); shadowRoot.appendChild(overlay); } // ... }
接下来,我们可以在 connectedCallback
方法中为 .drawer-overlay
添加点击事件。
// javascriptcn.com 代码示例 class MyDrawer extends HTMLElement { constructor() { super(); // ... // 监听 .drawer-overlay 的点击事件 this.shadowRoot.querySelector('.drawer-overlay').addEventListener('click', () => { this.close(); }); } // ... }
2.3 确保多个抽屉 / 侧边栏相互独立
在页面上使用多个抽屉 / 侧边栏时,我们需要确保它们相互独立。具体来说,我们需要确保:
- 每个抽屉 / 侧边栏的状态独立
- 点击一个抽屉 / 侧边栏的
.drawer-overlay
不会关闭其他抽屉 / 侧边栏
为了实现这个功能,我们可以利用 Shadow DOM 的隔离性。对于每个抽屉 / 侧边栏,我们都可以创建一个独立的 Shadow DOM,并在其中添加 .drawer-content
和 .drawer-overlay
元素。这样,每个抽屉 / 侧边栏就相互独立了。
// javascriptcn.com 代码示例 class MyDrawer extends HTMLElement { constructor() { super(); // 创建独立的 Shadow DOM const shadowRoot = this.attachShadow({ mode: 'open' }); // 创建抽屉 / 侧边栏的内容 const content = document.createElement('div'); content.classList.add('drawer-content'); content.innerHTML = this.innerHTML; // 创建遮盖层 const overlay = document.createElement('div'); overlay.classList.add('drawer-overlay'); // 将抽屉 / 侧边栏的内容和遮盖层添加到 Shadow DOM 中 shadowRoot.appendChild(content); shadowRoot.appendChild(overlay); } // ... }
3. 完整示例代码
// javascriptcn.com 代码示例 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Web Components 抽屉 / 侧边栏组件</title> <style> my-drawer { display: block; position: fixed; top: 0; bottom: 0; left: 0; width: 280px; background-color: #fff; box-shadow: 0 0 30px rgba(0, 0, 0, 0.1); transform: translateX(-100%); transition: transform 0.3s ease-out; } my-drawer[opened] { transform: translateX(0); } my-drawer .drawer-content { padding: 16px; } my-drawer .drawer-overlay { position: fixed; top: 0; bottom: 0; left: 0; right: 0; background-color: rgba(0, 0, 0, 0.5); opacity: 0; pointer-events: none; transition: opacity 0.3s ease-out; } my-drawer[opened] .drawer-overlay { opacity: 1; pointer-events: auto; } </style> </head> <body> <my-drawer id="drawer1"> <div class="drawer-content"> <h2>抽屉 1</h2> <p>这是抽屉 1 的内容。</p> </div> <div class="drawer-overlay"></div> </my-drawer> <my-drawer id="drawer2"> <div class="drawer-content"> <h2>抽屉 2</h2> <p>这是抽屉 2 的内容。</p> </div> <div class="drawer-overlay"></div> </my-drawer> <button onclick="openDrawer('drawer1')">打开抽屉 1</button> <button onclick="openDrawer('drawer2')">打开抽屉 2</button> <script> class MyDrawer extends HTMLElement { constructor() { super(); // 创建独立的 Shadow DOM const shadowRoot = this.attachShadow({ mode: 'open' }); // 创建抽屉 / 侧边栏的内容 const content = document.createElement('div'); content.classList.add('drawer-content'); content.innerHTML = this.innerHTML; // 创建遮盖层 const overlay = document.createElement('div'); overlay.classList.add('drawer-overlay'); // 将抽屉 / 侧边栏的内容和遮盖层添加到 Shadow DOM 中 shadowRoot.appendChild(content); shadowRoot.appendChild(overlay); // 初始化抽屉 / 侧边栏的状态 this._opened = false; } // 监听 opened 属性的变化 static get observedAttributes() { return ['opened']; } // 当 opened 属性发生变化时,更新抽屉 / 侧边栏的状态 attributeChangedCallback(name, oldValue, newValue) { if (name === 'opened') { this._opened = newValue !== null; this._update(); } } // 打开抽屉 / 侧边栏 open() { this._opened = true; this.setAttribute('opened', ''); this._update(); } // 关闭抽屉 / 侧边栏 close() { this._opened = false; this.removeAttribute('opened'); this._update(); } // 更新抽屉 / 侧边栏的状态 _update() { if (this._opened) { this.shadowRoot.querySelector('.drawer-overlay').style.pointerEvents = 'auto'; } else { this.shadowRoot.querySelector('.drawer-overlay').style.pointerEvents = 'none'; } } } window.customElements.define('my-drawer', MyDrawer); function openDrawer(id) { document.getElementById(id).open(); } </script> </body> </html>
4. 总结
在本文中,我们介绍了如何在 Web Components 中实现一个抽屉 / 侧边栏组件。具体来说,我们设计了组件结构,并实现了打开 / 关闭抽屉 / 侧边栏、点击 .drawer-overlay
关闭抽屉 / 侧边栏以及确保多个抽屉 / 侧边栏相互独立等功能。通过这个例子,我们可以看到 Web Components 的强大和灵活性,它可以让我们更方便地创建可复用的 UI 组件,提高开发效率和代码可维护性。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6578faced2f5e1655d2e4f9e