2022-08-28

Hide web component until browser knows what to do with it

Similar to this question: How to prevent flickering with web components?

But different in that I can't just set the inner HTML to nothing until loaded because there is slotted content, and I don't wish to block rendering the page while it executes the web component JS.

I thought I could add CSS to hide the element, and then the init of the webcomponent unhides itself, but then that CSS snippet needs to included where ever the web component is used, which is not very modular, and prone to be forgotten

I am working on modal component, here's the code (although I don't think its particularly relevant:

<div id="BLUR" part="blur" class="display-none">
    <div id="DIALOGUE" part="dialogue">
        <div id="CLOSE" part="close">
            X
        </div>
        <slot></slot>
    </div>
</div>
const name = "wc-modal";
const template = document.getElementById("TEMPLATE_" + name);

class Component extends HTMLElement {
    static get observedAttributes() { return ["open"]; } // prettier-ignore

    constructor() {
        super();
        this.attachShadow({ mode: "open" });
        this.shadowRoot.appendChild(template.content.cloneNode(true));
    }
    connectedCallback() {
        if (this.initialised) return; // Prevent initialising twice is item is moved
        this.setupEventListners();
        this.init();
        this._upgradeProperty("open");
        this.initialised = true;
    }
    init() {}
    get(id) {
        return this.shadowRoot.getElementById(id);
    }

    _upgradeProperty(prop) {
        /*
        Setting a property before the component has loaded will result in the setter being overriden by the value. Delete the property and reinstate the setter.
        https://developers.google.com/web/fundamentals/web-components/best-practices#lazy-properties
        */
        if (this.hasOwnProperty(prop)) {
            let value = this[prop];
            delete this[prop];
            this[prop] = value;
        }
    }

    // Setup Event Listeners ___________________________________________________
    setupEventListners() {
        this.get("CLOSE").addEventListener("click", () => this.removeAttribute("open"));
        this.get("BLUR").addEventListener("click", () => this.removeAttribute("open"));
        // If the dialogue does not handle click, it propagates up to the blur, and closes the modal
        this.get("DIALOGUE").addEventListener("click", (event) => event.stopPropagation());
    }

    // Attributes _____________________________________________________________
    attributeChangedCallback(name, oldValue, newValue) {
        switch (name) {
            case "open":
                // Disabled is blank string for true, null for false
                if (newValue === null) this.hideModal();
                else this.showModal();
        }
    }

    // Property Getters/Setters _______________________________________________
    get open() { return this.hasAttribute("open"); } // prettier-ignore
    set open(value) { value ? this.setAttribute("open", "") : this.removeAttribute("open"); } // prettier-ignore

    // Utils & Handlers _______________________________________________________
    showModal() {
        this.get("BLUR").classList.remove("display-none");
        // Disable scrolling of the background
        document.body.style.overflow = "hidden";
    }
    hideModal() {
        this.get("BLUR").classList.add("display-none");
        // Renable scrolling of the background
        document.body.style.overflow = "unset";
    }
}

window.customElements.define(name, Component);


No comments:

Post a Comment