HTML5 Custom Elements通过customElements.define()注册继承HTMLElement的类,支持影子DOM、属性监听、插槽及自定义事件,命名须含短横线,原生兼容主流浏览器。

在HTML5中使用Custom Elements创建可复用UI组件,核心是通过 customElements.define() 注册一个继承自 HTMLElement 的类,并在其生命周期中封装结构、样式和行为。它不依赖框架,原生支持,浏览器兼容性良好(Chrome 67+、Firefox 63+、Safari 13.1+、Edge 79+)。
定义基础结构与注册组件
自定义元素名必须包含短横线(-),如 <my-button>,这是强制规范,用于区分原生标签。
以下是一个带文本和点击反馈的按钮组件示例:
class MyButton extends HTMLElement {
constructor() {
super();
// 创建影子根(推荐:避免样式/结构污染)
this.attachShadow({ mode: 'open' });
// 定义默认文本(从属性读取或设为占位)
const label = this.getAttribute('label') || 'Click me';
// 渲染内部结构
this.shadowRoot.innerHTML = `
<style>
button {
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover { background: #0056b3; }
</style>
<button>${label}</button>
`;
// 绑定事件
this.shadowRoot.querySelector('button').addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('my-click', {
detail: { timestamp: Date.now() }
}));
});
}
}
// 注册组件(名称必须含短横线)
customElements.define('my-button', MyButton);
注册后即可在HTML中直接使用:
立即学习“前端免费学习笔记(深入)”;
<my-button label="Submit"></my-button>
<script>
document.querySelector('my-button').addEventListener('my-click', (e) => {
console.log('触发了自定义点击事件', e.detail);
});
</script>
响应属性变化(attributeChangedCallback)
若希望组件响应 HTML 属性更新(如 label 或 disabled),需声明 observedAttributes 并实现 attributeChangedCallback:
- 在类中添加静态 getter:
static get observedAttributes() { return ['label', 'disabled']; } - 实现回调函数,在其中更新 shadow DOM 或内部状态
- 注意:只有通过
setAttribute、removeAttribute或 HTML 初始化时设置的属性才会触发该回调;JS 直接赋值如el.label = 'new'不会触发(除非也重写 setter)
例如同步 disabled 状态到内部按钮:
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'disabled') {
const btn = this.shadowRoot.querySelector('button');
if (btn) {
btn.disabled = newValue !== null;
btn.style.opacity = newValue !== null ? '0.6' : '1';
}
}
}
支持插槽(Slot)实现内容分发
让组件支持传入任意 HTML 内容(类似 Vue 的 <slot> 或 React 的 children),使用 <slot> 标签即可:
- 在 shadow DOM 中放置
<slot></slot>作为内容出口 - 外部使用时,在开始与结束标签之间写内容,会被自动渲染进 slot 位置
- 支持具名 slot:
<slot name="icon"></slot>+<span slot="icon">?</span>
升级上面的按钮,支持自定义内容:
// 替换 innerHTML 中的 button 标签为:
`<style>...</style>
<button><slot>${label}</slot></button>`
然后这样使用:
<my-button> <strong>✅ Confirm</strong> </my-button>
封装常用逻辑与最佳实践
- 始终使用 Shadow DOM:隔离样式和结构,防止全局 CSS 污染,也避免组件内部被外部样式意外影响
-
属性与属性反射保持一致:若提供
disabled属性,建议同时实现disabledgetter/setter,使 JS 操作更自然 -
事件优先用 CustomEvent:避免使用原生事件名(如
click),防止与内置行为冲突;命名建议带前缀(如my-change) -
支持无障碍(a11y):手动设置
role、aria-属性,尤其当语义化标签(如<button>)被隐藏或替换时 - 避免在 constructor 中操作文档或发起网络请求:此时元素尚未插入 DOM,且构造函数应保持轻量
自定义元素不是黑魔法,而是标准化的扩展机制。写好一个,就能跨项目、跨框架复用——只要浏览器支持,它就在那儿,安静、可靠、无需打包配置。



















