必须先用customElements.define()注册卡片元素,标签名须含短横线(如card-item),类须继承HTMLElement且在DOMContentLoaded后注册;未注册时document.createElement('card-item')无效。

如何用 customElements.define() 注册可复用的卡片元素
直接用 document.createElement('card-item') 不会生效,必须先注册。浏览器只认已注册的自定义标签名,且名称中必须包含短横线(-),比如 card-item 合法,carditem 会报错。
- 注册前确保类继承
HTMLElement,否则无法挂载生命周期钩子 - 不能在
<head>中立即调用customElements.define(),DOM 尚未就绪;建议放在DOMContentLoaded事件后或模块顶层(ESM 环境下) - 若重复注册同一名字,会抛出
DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "card-item" has already been used
基础注册示例:
class CardItem extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
:host { display: block; border: 1px solid #eee; border-radius: 8px; }
.content { padding: 16px; }
</style>
<div class="content"><slot></slot></div>
`;
}
}
customElements.define('card-item', CardItem);
为什么必须用 Shadow DOM 而不是普通子节点
不用 shadowRoot,样式和结构就暴露在外,父页面的 CSS 可能意外覆盖卡片内部布局,比如全局设置了 h2 { margin: 0 },你的卡片标题就塌了——这在多人协作或引入第三方库时极常见。
-
Shadow DOM提供样式封闭性,外部 CSS 无法穿透,:host是唯一可控入口 - 插槽(
<slot>)是内容分发核心,不写它,传进<card-item>Hello</card-item>的文本不会渲染 - 若用
{ mode: 'closed' },JavaScript 也无法从外部访问shadowRoot,调试困难,日常开发推荐'open'
attributeChangedCallback 怎么响应卡片状态变化
卡片常需根据属性切换展开/收起、禁用、主题色等状态,但 observedAttributes 必须显式声明,否则 attributeChangedCallback 根本不触发。
立即学习“前端免费学习笔记(深入)”;
- 只监听字符串属性:布尔值如
expanded实际传入的是空字符串或"expanded",需用this.hasAttribute('expanded')判断 - 数值属性(如
priority="2")仍为字符串,需手动parseInt()或parseFloat() - 避免在回调里直接操作
shadowRoot.innerHTML,频繁重写会清空事件监听器;推荐用this.shadowRoot.querySelector('.status').textContent = newValue
示例:
static get observedAttributes() {
return ['expanded', 'theme'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'expanded') {
this.shadowRoot.querySelector('.body').hidden = !this.hasAttribute('expanded');
}
}
如何让卡片支持响应式布局与外部 CSS 控制尺寸
:host 支持大部分 CSS 属性,但不能设 display: flex 或 grid——因为它是“宿主”,不是容器。真正控制排列方式的是卡片的父容器。
- 用
:host([expanded])匹配带属性的实例,实现条件样式 - 允许外部通过
width、max-width、margin控制卡片尺寸,但不要在 shadow 内设width: 100%,否则会被父级约束截断 - 若需内部网格布局,应在 shadow 内部用
.container { display: grid },而非依赖:host - 媒体查询写在 shadow 内部有效,但注意:CSS 变量(
--card-padding)可从外部传入并被:host使用,这是跨边界通信最轻量的方式
容易忽略的一点:自定义元素默认是 display: inline,多数场景下得在 :host 里显式设 display: block 或 inline-block,否则 margin/padding 表现异常。



















