5

I'm experimenting with native web components, and so far I actually really like them.

I have created a microsite that has a homepage, about page, and a contact page, and a menu that works by changing the hash component of the URL.

However, I'm noticing an undesirable effect - whenever I switch from one "page" to another (which in reality, is just unmounting one native web component, and mounting another), for about half a second, I see the web component render with default styles, before painting with the styles that I've included within the shadow DOM.

Initially, my component was like this:

    const template = document.createElement('template');
    template.innerHTML = `
        <style>
            @import '../reset.css';
            @import '../vars.css';
            @import '../bodyText.css';
        </style>
        <div class="body-text">
            <h1>Home page</h1>
            <p>Home. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
        </p>
    `;

    class HomePage extends HTMLElement {
        constructor() {
            super();
            this.attachShadow({ mode: 'open' });
            this.shadowRoot.append(template.content.cloneNode(true));
        }
    }

    export default HomePage;

I've tried to remedy the problem by removing the CSS import, and importing styles like this instead:

    function createStylesheet(path) {
        const linkElem = document.createElement('link');
        linkElem.setAttribute('rel', 'stylesheet');
        linkElem.setAttribute('href', path);
        return linkElem;
    }

    const s1 = createStylesheet('../reset.css');
    const s2 = createStylesheet('../vars.css');
    const s3 = createStylesheet('../bodyText.css');

    const template = document.createElement('template');
    template.innerHTML = `
        <div class="body-text">
            <h1>Home page</h1>
            <p>Home. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
        </p>
    `;

    class HomePage extends HTMLElement {
        constructor() {
            super();
            this.attachShadow({ mode: 'open' });
            this.shadowRoot.append(template.content.cloneNode(true));
            this.shadowRoot.append(s1);
            this.shadowRoot.append(s2);
            this.shadowRoot.append(s3);
        }
    }

    export default HomePage;

But still, there is a flash of unstyled content whenever the component is mounted.

This wouldn't be so much of a problem if it only happened when a component of a certain type first loaded, but the artefact is visible every time a custom component is mounted.

Is there any way to stop this from happening? I'd even happily forego the shadow DOM and just use BEM if it meant I could use nice, lightweight native web components!

I've heard its possible to prevent it from happening by using inline styles, but I'd prefer not to go down that route...I'd prefer to keep the CSS in a well-organised set of subdirectories.

kohloth
  • 742
  • 1
  • 7
  • 21

3 Answers3

4

Elaborating on @earlAchromatic's answer and Google's recommendation.

In order to prevent a reflow of other content you can set the probable height and display: of the final element if you can.

In order to not show slotted content, we start with opacity:0.

your-custom-component {
    display: block;
    /* Be as precise as you can */
    min-height: 2em;
    transition: opacity 0.5s ease-out;
}
your-custom-component:not(:defined) {
    opacity: 0;
}

Of course, you can also style slotted elements this way

Jonas Eberle
  • 2,835
  • 1
  • 15
  • 25
  • +1 A very interesting approach. However: You still have to keep those rules separate from the actual components (that you may ship individually) and setting a `min-height` might be a problem if the component happens to be smaller than what you specify there. You basically have to take the "smallest" (in height) occurrence of your custom element, which might not be what the result eventually will be. I really hope there will be a solution for those problems, but ATM i don't see how this could be addressed other than loading the component in the `` section. – codepleb Nov 17 '21 at 10:33
  • If you know what size your WC will have you can use it there (either like this, statically, or as an inline style set when rendering the HTML if you can for example make a better judgement at runtime). Unfortunately WCs are often dynamic and knowing or even guessing the size beforehand can be tedious. – Jonas Eberle Nov 17 '21 at 15:36
1

I still had issues even with dev tools closed, so for anyone who ends up here trying to solve a FOUC issue when using web components, the solution that worked for me was to use the :defined psuedo-class to target the component and pre-style it before it is registered via javascript.

So something like:

web-component:not(:defined) {
  display: block;
  height: 100vh;
  opacity: 0;
  transition: opacity 0.5s ease-in-out;
  }

When the component is registered, the selector no longer applies to the component, and the flash is avoided.

See google's documentation of the solution here: https://developers.google.com/web/fundamentals/web-components/customelements#prestyle

0

Edit: While typing this, I've found that the FOUC only happens when Google Chrome devtools are open, but not when they are closed. Maybe the behaviour happens because the stylesheets are never cached when devtools is open...

kohloth
  • 742
  • 1
  • 7
  • 21
  • 1
    No, FOUC and in consequence Cumulative Layout Shifts (CLS) happen with or without developer tools. Feed Lighthouse [with this basic MDN example](https://mdn.github.io/web-components-examples/expanding-list-web-component/). – Stefan Jun 23 '22 at 00:57