Web Component 简单示例

前言

Web Component: 创建可重用的定制元素

学习内容来源:

调试设置:

谷歌浏览器:设置------preferences------Elements------Show user agent shadow DOM打钩

基本概念

  • Custom element(自定义元素):class或者function,定义组件api。
  • Shadow DOM(影子 DOM):附加到元素中,可隔离样式。
  • HTML template(HTML 模板): <template><slot> 可以编写不在呈现页面中显示的标记模板。

自定义组件

  1. 注册组件
js 复制代码
// TodoItem.js
class TodoItem extends HTMLElement{}
customElements.define("todo-item", TodoItem)  // 传入组件名称与组件
html 复制代码
<!--index.html-->
<!--使用-->
<todo-item></todo-item>
  1. 创建模板
js 复制代码
// TodoItem.js
const template=document.createElement("template")
template.innerHTML=`
    <style>
        label{
            display: block;
        }
        .description{
            color: #a9a9a9;
            font-size: .8em;
        }
    </style>

    <label>
        <input type="checkbox" />
        <slot></slot>
        <span class="description">
            <slot name="description"></slot>
        </span>
    </label>
`

也可以直接写在html中

html 复制代码
<!--index.html-->
<template id="myTemp">
    <style>
        label{
            display: block;
        }
        .description{
            color: #a9a9a9;
            font-size: .8em;
        }
    </style>

    <label>
        <input type="checkbox" />
        <slot></slot>
        <span class="description">
            <slot name="description"></slot>
        </span>
    </label>
<template>
  1. 开启shadow dom
js 复制代码
// TodoItem.js
class TodoItem extends HTMLElement{
    constructor(){
        super()
        // ++ 开启shadow dom
        const shadowDom = this.attachShadow({ mode : "open"})
        // ++ 如果模板是写在js中,直接克隆模板,并把模板加入到shadow dom中:
        shadowDom.appendChild(template.content.cloneNode(true))
        
        // 否则需要获取一下模板: 
        // let template = ducoment.querySelector('#myTemp')
        // shadowDom.appendChild(template.content.cloneNode(true))
    }
}

现在可以在插槽中加入一些内容。在模板中设置的<style>将不会影响到外边的元素:

html 复制代码
<todo-item>
    <!--内容会加到默认slot中 -->
    todo1    
    
    <!--内容会加到description slot中 -->
    <span slot="description">其他描述</span> 
</todo-item>
  1. 生命周期
js 复制代码
// TodoItem.js

class TodoItem extends HTMLElement{
    constructor(){
        super()
        const shadowDom = this.attachShadow({ mode : "open"})
        shadowDom.appendChild(template.content.cloneNode(true))
    }
    
	// ++ 自定义元素挂载时被调用
	connectedCallback() {}
	// ++ 自定义元素卸载时被调用
	disconnectedCallback() {}
}

内置元素扩展与组件属性

  1. 注册组件:继承ul标签
js 复制代码
// ExpandableList.js
class ExpandableList extends HTMLUListElement {}

customElements.define('expandable-list', ExpandableList, {
   extends: 'ul',
});
  1. 定义组件
js 复制代码
class ExpandableList extends HTMLUListElement {
   constructor() {
      super();
      this.style.position = 'relative';

	  // 创建一个子元素
      this.toggleBtn = document.createElement('button');
      this.toggleBtn.style.position = 'absolute';
      this.toggleBtn.style.border = 'none';
      this.toggleBtn.style.background = 'none';
      this.toggleBtn.style.padding = 0;
      this.toggleBtn.style.top = 0;
      this.toggleBtn.style.left = '5px';
      this.toggleBtn.style.cursor = 'pointer';
      this.toggleBtn.innerText = '>';
      this.appendChild(this.toggleBtn);
      
	 // 定义点击事件
      this.toggleBtn.addEventListener('click', () => {
         this.dataset.expanded = !this.isExpanded;
      });

      // 获取某个属性
      console.log(this.getAttr('name'))
   }

   // 获取属性
    private getAttr (key) {
		return this.getAttribute(key)
	}

   // 计算属性
   get isExpanded() {
      return (
         this.dataset.expanded !== 'false' && this.dataset.expanded !== null
      );
   }

   // 监听属性
   static get observedAttributes() {
      return ['data-expanded'];
   }

  // 监听到上方属性变化后调用
   attributeChangedCallback(name, oldValue, newValue) {
      this.updateStyles();
   }


   updateStyles() {
      const transform = this.isExpanded ? 'rotate(90deg)' : '';
      this.toggleBtn.style.transform = transform;
      [...this.children].forEach((child) => {
         if (child !== this.toggleBtn) {
            child.style.display = this.isExpanded ? '' : 'none';
         }
      });
   }
   
   // 挂载后调用
   connectedCallback() {
      this.updateStyles();
   }
}

customElements.define('expandable-list', ExpandableList, {
   extends: 'ul',
});
  1. 使用:通过is传入扩展
html 复制代码
<ul is="expandable-list" data-expanded name="myul">
    <li>apple</li>
    <li>banana</li>
</ul>
相关推荐
萧行之40 分钟前
Ubuntu Node.js 版本管理工具 n 完整安装与使用教程
linux·前端
devlei7 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
Jagger_8 小时前
周末和AI肝了两天,终于知道:为什么要把AI当做实习生
前端
weixin_456164838 小时前
vue3 子组件向父组件传参
前端·vue.js
沉鱼.448 小时前
第十二届题目
java·前端·算法
Setsuna_F_Seiei9 小时前
CocosCreator 游戏开发 - 多维度状态机架构设计与实现
前端·cocos creator·游戏开发
Bigger9 小时前
CodeWalkers:让 AI 助手化身桌面宠物,陪你敲代码的赛博伙伴!
前端·app·ai编程
cyclv10 小时前
无网络地图展示轨迹,地图瓦片下载,绘制管线
前端·javascript
土豆125010 小时前
Tauri 入门与实践:用 Rust 构建你的下一个桌面应用
前端·rust
小陈工12 小时前
2026年4月2日技术资讯洞察:数据库融合革命、端侧AI突破与脑机接口产业化
开发语言·前端·数据库·人工智能·python·安全