N_G
N_G

Reputation: 51

JS function for Darkmode / Lightmode with value checking via Local Storage gives me a quick color change when going on subpages

I would like to add a button for switching to darkmode and lightmode on my website.

I use var()'s in my css to control the color of my elements. This is my code:

function loadMode() {
    const mode = localStorage.getItem('mode');
    if (mode === 'dark') {
        setDarkMode();
    }
}

// set darkmode
function setDarkMode() {
    document.documentElement.style.setProperty('--textColor', '#eaeaea');
    document.documentElement.style.setProperty('--backgroundColor', '#333333');
    document.getElementById('toggleMode').textContent = 'Wechsel zu Lightmode';
    localStorage.setItem('mode', 'dark');
}

// set lightmode
function setLightMode() {
    document.documentElement.style.setProperty('--textColor', '#313131');
    document.documentElement.style.setProperty('--backgroundColor', '#e0e0e0');
    document.getElementById('toggleMode').textContent = 'Wechsel zu Darkmode';
    localStorage.setItem('mode', 'light');
}

// toggle the color mode
function toggleMode() {
    const isDarkMode = localStorage.getItem('mode') === 'dark';
    if (isDarkMode) {
        setLightMode();
    } else {
        setDarkMode();
    }
}

// event listener for button
document.getElementById('toggleMode').addEventListener('click', toggleMode);

// load mode on site load
loadMode();

This script is loaded on the end of the webpage (this is the problem, I know, but how can I fix it?) Now I have the problem that every time I go to a subpage the website is being loaded with the Light Colors and then switched to the dark colors which results in a quick, but very annoying color flicker effect.

How can I prevent this form happening? My website is build with php so sessions could work? Or cookies?

Thank you for helping!

I tried to put the function in the header but then the body element cant receive the color change because I think its not loaded yes(?)

Upvotes: 0

Views: 85

Answers (4)

Rounin
Rounin

Reputation: 29463

One approach would be to query the localStorage early on in the <head> of the document and apply an attribute to the <html> element.

<html>
<head>
<script>
  document.documentElement.dataset.screenMode = localStorage.getItem('screenMode');
</script>
</head>
<body>
</body>
</html>

Once you've done that, everything else can live in your .css stylesheet.

:root {
  --textColor: #313131;
  --backgroundColor: #e0e0e0;
}

:root[data-screen-mode="dark"] {
  --textColor: #eaeaea;
  --backgroundColor: #333333;
}

Upvotes: 1

Heiko Thei&#223;en
Heiko Thei&#223;en

Reputation: 16728

Flickering is completely avoided if the desired CSS properties are already sent by the server depending on the user setting. To make this possible, the user setting must not be remembered via localStorage.getItem and localStorage.setItem but in a cookie, via cookieStore.get and cookieStore.set.

The following snippet shows a server-side implementation with express and ejs:

app.js

app.get("/", function(req, res) {
  var mode = (req.get("Cookie") || "").match(/(^|; )mode=(.*?)($|; )/)?.[2];
  res.render("page.ejs", {mode});
});

page.ejs

<!DOCTYPE html><html>
  <head>
    <style type="text/css">
      :root {
        --textColor: <%= mode === "dark" ? "#eaeaea" : "#313131" %>;
        --backgroundColor: <%= mode === "dark" ? "#333333" : "#e0e0e0" %>;
      }
      /* Your other styles */
    </style>
    ...
  </head>
  <body>...</body>
</html>

Upvotes: 0

chrwahl
chrwahl

Reputation: 13135

Make sure that your JavaScript is executed as soon as possible. It is always a good idea to have you JavaScript defined in the <head> element. You can add the defer attribute to the script tag to make sure that the code is executed before the DOMContentLoaded event. But having the inline JavaScript in the <head> will also do that.

The event DOMContentLoaded is fired when the DOM is ready and you can start manipulating the DOM. When that happens you can set the (dark/light) mode. In the following example I removed a lot of the JavaScript. The less you manipulate the DOM, the better. And I rely on CSS to switch between the two different modes.

In the example I comment out the code for localStorage, because it doesn't work on StackOverflow.

:root {
  --textColor: #313131;
  --backgroundColor: #e0e0e0;
}

:root:has(form[name="mode"] input:checked) {
  --textColor: #eaeaea;
  --backgroundColor: #333333;
}

body {
  color: var(--textColor);
  background-color: var(--backgroundColor);
}

form[name="mode"] input {
  display: none;
}

form[name="mode"] label {
  cursor: pointer;
  background-color: gray;
  border: thin black solid;
  border-radius: .2em;
  padding: .2em;
}

form[name="mode"] label::before {
  content: "Wechsel zu Darkmode";
}

form[name="mode"] label:has(input:checked)::before {
  content: "Wechsel zu Lightmode";
}
<html>
  <head>
    <script defer>
      document.addEventListener('DOMContentLoaded', e => {
        //document.forms.mode.dark.checked = localStorage.mode === 'dark';
        // test in the absence of localStorage:
        document.forms.mode.dark.checked = false;
        
        document.forms.mode.dark.addEventListener('input', e => {
          //localStorage.mode = e.target.checked ? 'dark' : 'light';
          // test in the absence of localStorage:
          console.log('mode is now:', e.target.checked ? 'dark' : 'light');
        });
      });
    </script>
  </head>
  <body>
    <form name="mode">
      <label><input type="checkbox" name="dark"></label>
    </form>
  </body>
</html>

Upvotes: 0

N_G
N_G

Reputation: 51

I fixed it myself:

Put part of the script in the :

<script>
    (function() {
        const mode = localStorage.getItem('mode');
        if (mode === 'dark') {
            document.documentElement.style.setProperty('--textColor', '#eaeaea');
            document.documentElement.style.setProperty('--backgroundColor', '#333333');
        } else {
            document.documentElement.style.setProperty('--textColor', '#313131');
            document.documentElement.style.setProperty('--backgroundColor', '#e0e0e0');
        }
    })();
</script>

And put the rest of the code at the end of the body:

<script>
function loadMode() {
    const mode = localStorage.getItem('mode');
    if (mode === 'dark') {
        setDarkMode();
    }
}

function setDarkMode() {
    document.documentElement.style.setProperty('--textColor', '#eaeaea');
    document.documentElement.style.setProperty('--backgroundColor', '#333333');
    document.getElementById('toggleMode').textContent = 'Wechsel zu Lightmode';
    localStorage.setItem('mode', 'dark');
}

function setLightMode() {
    document.documentElement.style.setProperty('--textColor', '#313131');
    document.documentElement.style.setProperty('--backgroundColor', '#e0e0e0');
    document.getElementById('toggleMode').textContent = 'Wechsel zu Darkmode';
    localStorage.setItem('mode', 'light');
}

function toggleMode() {
    const isDarkMode = localStorage.getItem('mode') === 'dark';
    if (isDarkMode) {
        setLightMode();
    } else {
        setDarkMode();
    }
}

document.getElementById('toggleMode').addEventListener('click', toggleMode);

// Load mode on page load
loadMode();

Upvotes: 1

Related Questions