纯HTML中使用shadowRoot自定义组件

发布于:2025-07-03 ⋅ 阅读:(22) ⋅ 点赞:(0)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Complex Custom Element</title>
    <style>
        /* 全局样式 */
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }

        .global-text {
            color: blue;
        }
    </style>
</head>

<body>
    <h1>复杂自定义元素示例:可折叠卡片</h1>

    <!-- 普通段落(受全局样式影响) -->
    <p class="global-text">这是全局样式的段落,颜色为蓝色。</p>

    <!-- 自定义卡片组件 -->
    <collapse-card title="用户信息">
        <div slot="content">
            <p>姓名:张三</p>
            <p>年龄:30</p>
            <p>职业:前端工程师</p>
        </div>
    </collapse-card>

    <collapse-card title="项目详情">
        <div slot="content">
            <p>项目名称:Web Components 实践</p>
            <p>状态:进行中</p>
            <p>截止日期:2023-12-31</p>
        </div>
    </collapse-card>

    <script>
        class CollapseCard extends HTMLElement {
            constructor() {
                super();
                // 创建影子 DOM(open 模式)
                const shadowRoot = this.attachShadow({ mode: 'open' });
                // 初始化属性
                this._isOpen = false;
                // 定义组件模板
                shadowRoot.innerHTML = `
          <style>
            :host {
              display: block;
              margin-bottom: 10px;
              border: 1px solid #ddd;
              border-radius: 4px;
              overflow: hidden;
            }
            .card-header {
              padding: 10px;
              background-color: #f5f5f5;
              cursor: pointer;
              display: flex;
              justify-content: space-between;
              align-items: center;
            }
            .card-content {
              padding: 10px;
              display: none;
              background-color: white;
            }
            .card-content.show {
              display: block;
            }
            .toggle-icon::after {
              content: "▼";
              transition: transform 0.3s;
            }
            .toggle-icon.open::after {
              transform: rotate(180deg);
            }
          </style>
          <div class="card-header">
            <span class="card-title">${this.getAttribute('title') || '默认标题'}</span>
            <span class="toggle-icon"></span>
          </div>
          <div class="card-content">
            <slot name="content"></slot>
          </div>
        `;

                // 绑定事件
                shadowRoot.querySelector('.card-header').addEventListener('click', () => this.toggle());
            }

            // 监听属性变化
            static get observedAttributes() {
                return ['title'];
            }

            attributeChangedCallback(name, oldValue, newValue) {
                if (name === 'title') {
                    this.shadowRoot.querySelector('.card-title').textContent = newValue;
                }
            }

            // 切换折叠状态
            toggle() {
                this._isOpen = !this._isOpen;
                const content = this.shadowRoot.querySelector('.card-content');
                const toggleIcon = this.shadowRoot.querySelector('.toggle-icon');

                content.classList.toggle('show', this._isOpen);
                toggleIcon.classList.toggle('open', this._isOpen);

                // 触发自定义事件
                this.dispatchEvent(new CustomEvent('toggle', {
                    detail: { isOpen: this._isOpen }
                }));
            }
        }

        // 注册自定义元素
        customElements.define('collapse-card', CollapseCard);

        // 监听自定义事件
        document.querySelectorAll('collapse-card').forEach(card => {
            card.addEventListener('toggle', (e) => {
                console.log(`卡片状态:${e.detail.isOpen ? '展开' : '折叠'}`);
            });
        });
    </script>
</body>

</html>