DEV Community

Tianya School
Tianya School

Posted on

Web Components: Custom Elements and Shadow DOM in Action

Web Components is a set of technologies used in modern web development to create reusable and encapsulated custom HTML elements. It includes Custom Elements, Shadow DOM, HTML Templates, and Slots.

Define custom elements

Define a new HTML element, which can be done through the customElements.define method

class MyElement extends HTMLElement {
    constructor() {
        super(); // Call the superclass constructor
        this.attachShadow({ mode: 'open' }); // Create Shadow Root
    }

    connectedCallback() {
        this.shadowRoot.innerHTML = `
        <style>
        /* Define the style inside Shadow DOM here */
        </style>
        <slot>Default content</slot>
        `;
    }
}

customElements.define('my-element', MyElement);
Enter fullscreen mode Exit fullscreen mode

Encapsulate styles with Shadow DOM

Shadow DOM allows us to define private CSS styles inside a component, which only affects the elements inside the component and will not leak to the external DOM. In the connectedCallback above, the Shadow Root is created and styles are added:

this.shadowRoot.innerHTML = `
    <style>
    div {
    color: blue;
    font-size: 24px;
    }
    </style>
    <div><slot></slot></div>
    `;
Enter fullscreen mode Exit fullscreen mode

Any text inside <my-element> will use a blue font and a size of 24px.

Inserting content

Using the <slot> element, we can allow users to insert content into custom elements, and these contents will be inserted into the corresponding position in the Shadow DOM:

<my-element>
    <span>This is the inserted content</span>
</my-element>
Enter fullscreen mode Exit fullscreen mode

In the MyElement class above, the <slot> element will display the content inserted by the user.

Interactions and events

Custom elements can have their own set of events and interaction logic. For example, you can add an event listener:

class MyElement extends HTMLElement {
    // ...

    disconnectedCallback() {
    // Clean up resources or perform disconnection operations
    }

    // Add event listeners
    buttonClickHandler() {
        console.log('Button clicked!');
    }

    connectedCallback() {
        // ...
        this.shadowRoot.querySelector('button').addEventListener('click', this.buttonClickHandler.bind(this));
    }
}
Enter fullscreen mode Exit fullscreen mode

Reuse and combination

Custom elements can be nested in other custom elements or reused in multiple places to achieve component reuse.

<div>
    <my-element>
        <button>Click me</button>
    </my-element>
    <my-element>
        <button>Click me again</button>
    </my-element>
</div>
Enter fullscreen mode Exit fullscreen mode

Both <my-element> can respond to click events, and their styles and logic are encapsulated in their respective Shadow DOMs without interfering with each other.

Attributes and Attribute Observation

To make custom elements more flexible and configurable, we can define attributes for them and observe changes in these attributes to responsively update the state or UI inside the component.

Define attributes
In the custom element class, you can declare the list of attributes to be observed through the observedAttributes static property:

static get observedAttributes() {
    return ['my-attribute'];
}
Enter fullscreen mode Exit fullscreen mode

Response to attribute changes
Then, respond to attribute changes by overriding the attributeChangedCallback method:

attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'my-attribute') {
        console.log(`my-attribute changed from ${oldValue} to ${newValue}`);
    // Update UI or logic based on attribute changes
    }
}
Enter fullscreen mode Exit fullscreen mode

Use attributes
In HTML, you can set these attributes through custom element tags:

<my-element my-attribute="someValue"></my-element>

Enter fullscreen mode Exit fullscreen mode

Style isolation and penetration

Shadow DOM provides style isolation, but sometimes we may want some global styles to also affect the Shadow DOM. You can use the CSS :host pseudo-class to control the style of the custom element itself, while :host-context(selector) allows you to change the style based on the host context.

If you need to affect the style inside the Shadow DOM from the outside, you can use CSS variables (Custom Properties):

/* Define variables in global styles or parent components */
:root {
    --my-color: blue;
}

/* Use these variables in the Shadow DOM */
<style>
    div {
        color: var(--my-color);
    }
</style>
Enter fullscreen mode Exit fullscreen mode

Lifecycle methods

In addition to connectedCallback, disconnectedCallback, and attributeChangedCallback, custom elements have other lifecycle methods, such as adoptedCallback, which is called when the element is moved to a new document.

Performance considerations

  • Lazy loading and on-demand creation: Ensure that custom elements are created and loaded only when needed to avoid unnecessary performance loss.

  • Optimize Shadow DOM: Minimize the depth and complexity of Shadow DOM, and avoid overuse of complex CSS selectors, as they may affect rendering performance.

Cross-framework compatibility

Web Components are designed as native web standards, which means they can work in any browser that supports Web Components, whether using Angular, React or Vue and other front-end frameworks, can be seamlessly integrated.

Top comments (0)