skip to Main Content

I’ve a javascript module that retrieves the user preference from localStorage, and then applies a class on the <html> tag, so my css looks like this:

:root.lightMode {
    --text: #030202;
    --background: #f0efef;
}

:root.darkMode {
    --text: #f9f4f4;
    --background: #100f0f;
}

:root {
    --text: #030202;
    --background: #f0efef;
}

The problem is that, when I reload the site (it loads really fast if it’s relevant) the default style (in this case the light theme included in the classless :root) flashes before changing to the desired style.

You can check the website that the script is being used here, but be aware that it is pt-br: https://douglasflorindo.github.io/FelpsTodoDiaAtlas/acheOFelps.html

The JS Module:

export let colorMode = "auto", localStorageAvailable = false;

export function updateColorMode(useLocalStorage, colorModePreference) {
   /*useLocalStorage = true | false;
   colorModePreference = null | "auto" | "light" | "dark";*/

   //Removes color mode classes from root then adds the desired one:
   function switchRootClass(className) {
       const rootClassList = document.documentElement.classList;

       if (rootClassList.contains("lightMode")) {
           rootClassList.remove("lightMode");
       };
       if (rootClassList.contains("darkMode")) {
           rootClassList.remove("darkMode");
       };

       rootClassList.add(className);
   };
   
   //Check if localStorage is available:
   function checkLocalStorage() {
       try {
           localStorage.setItem("test", "test");
           localStorage.removeItem("test");
           return true;
       } catch (error) {
           return false;
       }
   };

   if (useLocalStorage === true && checkLocalStorage()) {
       localStorageAvailable = true;
       //Check if localStorage item exist:
       if (!localStorage.getItem("colorMode")) {
           localStorage.setItem("colorMode", "auto");
       };
   } else {
       localStorageAvailable = false;
   };

   //Update colorMode to localStorage item or custom value: 
   if (colorModePreference === "auto" || colorModePreference === "light" || colorModePreference === "dark") {
       localStorageAvailable === true ? localStorage.setItem("colorMode", colorModePreference) : null;
       colorMode = colorModePreference;
   } else {
       localStorageAvailable === true ? colorMode = localStorage.getItem("colorMode") : null;
   };

   switch (colorMode) {
       case "auto":            
           window.matchMedia("(prefers-color-scheme: dark)").matches ?
               switchRootClass("darkMode")
               :
               switchRootClass("lightMode");

           //AddEventListeners to window so that it also updates when browser's color mode is altered:
           const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
           const lightModePreference = window.matchMedia("(prefers-color-scheme: light)");

           darkModePreference.addEventListener("change", function darkModeListener(e){e.matches && colorMode === "auto" ? updateColorMode(false, "auto") : null});
           lightModePreference.addEventListener("change", function lightModeListener(e){e.matches && colorMode === "auto" ? updateColorMode(false, "auto") : null});
           break;
       case "light":
           switchRootClass("lightMode");
           break;
       case "dark":
           switchRootClass("darkMode");
           break;
       default:
           break;
   };

}

My main js file:

import { updateColorMode } from './colorModeModule.js';

document.onreadystatechange = () => {
    if (document.readyState === 'complete'){
        updateColorMode(true, null);
    }
};

2

Answers


  1. Your code is waiting for readyState to be "complete", which is when "the …document and all sub-resources have finished loading" (my emphasis). There’s no reason to wait until all the sub-resources have been loaded just to read localStorage and update the classes on the body element. (You don’t even need to wait for the document to be fully parsed, as long as the code runs after the opening <body> tag has been parsed.)

    JavaScript standard modules aren’t executed until after the DOM has been fully parsed; type="module" implies defer by default (in essence, waiting for readyState to be "interactive"). So just removing that wait on readyState may be sufficient to fix the problem.

    If not, it may be that you’re getting the flash of unstyled content (or at least, flash of semi-styled content) because the code doesn’t get a chance to run until the HTML parsing is entirely finished. You can make the module execute sooner by adding the async attribute to it, as shown by this image from the specification link above:

    enter image description here
    But it could be that you have parts of your code that do need to wait for the HTML parsing to be complete, in which case that wouldn’t be appropriate.

    Alternatively, you may want a small targeted inline script to add the relevant class to body in a really proactive way. Reading the preference from localStorage and adding the class is, after all, a very small amount of code, and an inline script just after <body> won’t hold things up much at all.

    Login or Signup to reply.
  2. I suggest placing and executing the updateColorMode function directly inside the <head> section. That will ensure that the class name is set on the html element before any part of the body element is rendered.

    Changing the class name anytime after the body element is seen can cause FOUC since you are, for example, setting a theme-based background color on the body; and the background color could display long before the rest of the page is downloaded/displayed.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search