Custom Elements API
Defines a custom HTML element with lifecycle callbacks, observed attributes, and Shadow DOM encapsulation for reusable component creation.
Code
<custom-counter start="10" step="2"></custom-counter>
<script>
class CustomCounter extends HTMLElement {
static get observedAttributes() {
return ['start', 'step'];
}
constructor() {
super();
this._count = 0;
this._step = 1;
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
:host { display: inline-flex; gap: 0.5rem; align-items: center; }
button { padding: 0.25rem 0.5rem; cursor: pointer; }
.value { font-weight: bold; min-width: 2rem; text-align: center; }
</style>
<button id="dec">-</button>
<span class="value">0</span>
<button id="inc">+</button>
`;
}
connectedCallback() {
this._count = parseInt(this.getAttribute('start') || '0');
this._step = parseInt(this.getAttribute('step') || '1');
this._update();
this.shadowRoot.getElementById('inc')
.addEventListener('click', () => this._increment());
this.shadowRoot.getElementById('dec')
.addEventListener('click', () => this._decrement());
}
attributeChangedCallback(name, oldVal, newVal) {
if (name === 'step') this._step = parseInt(newVal || '1');
}
disconnectedCallback() {
console.log('Counter removed from DOM');
}
_increment() { this._count += this._step; this._update(); }
_decrement() { this._count -= this._step; this._update(); }
_update() {
this.shadowRoot.querySelector('.value').textContent = this._count;
this.dispatchEvent(new CustomEvent('change', {
detail: { value: this._count }
}));
}
}
customElements.define('custom-counter', CustomCounter);
document.querySelector('custom-counter')
.addEventListener('change', e => console.log('Count:', e.detail.value));
</script>Report a problem with this exampleOpens a pre-filled issue on GitHub.