admin管理员组

文章数量:1201790

I am trying to add this dark mode feature in my app. It uses localstorage to store the user's preference for future usage. So the problem now is when the dark mode is enabled, and the page is reloaded for some reason, eg. if the user deliberately reloads the page, or submits a form, then there's a flicker of white background all over the page before it turns to be dark. It stays a fraction of a second. It just doesn't look professional.

Haven't found any solution yet. So please help me out.

PS. The snippet below won't work here in SO as the code includes localStorage object.

Here's the code:

const toggleSwitch = document.querySelector('#dark-mode-button input[type="checkbox"]');
const currentTheme = localStorage.getItem('theme');

if (currentTheme) {
    document.documentElement.setAttribute('data-theme', currentTheme);
    if (currentTheme === 'dark') {
            toggleSwitch.checked = true;
    }
}

function switchTheme(e) {
    if (e.target.checked) {
        document.documentElement.setAttribute('data-theme', 'dark');
        localStorage.setItem('theme', 'dark');
    }else {        
        document.documentElement.setAttribute('data-theme', 'light');
        localStorage.setItem('theme', 'light');
    }    
}

toggleSwitch.addEventListener('change', switchTheme, false);  
:root {
  --primary-color: #495057;
  --bg-color-primary: #F5F5F5;
}

body{
  background-color: var(--bg-color-primary); 
}

[data-theme="dark"] {
  --primary-color: #8899A6;
  --bg-color-primary: #15202B;
}

table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
  background-color: #fff;
}

td, th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}
<div id="dark-mode-button">
    <input id="chck" type="checkbox">Dark Mode
    <label for="chck" class="check-trail">
      <span class="check-handler"></span>
    </label>
</div>

<table class="table">
    <thead>
      <tr>
          <th>Header 1</th>
          <th>Header 2</th>
          <th>Header 3</th>
      </tr>
    </thead>  
    <tbody>
      <tr>
        <td>Alfreds Futterkiste</td>
        <td>Maria Anders</td>
        <td>Germany</td>
      </tr>
    </tbody>                     
</table>

I am trying to add this dark mode feature in my app. It uses localstorage to store the user's preference for future usage. So the problem now is when the dark mode is enabled, and the page is reloaded for some reason, eg. if the user deliberately reloads the page, or submits a form, then there's a flicker of white background all over the page before it turns to be dark. It stays a fraction of a second. It just doesn't look professional.

Haven't found any solution yet. So please help me out.

PS. The snippet below won't work here in SO as the code includes localStorage object.

Here's the code:

const toggleSwitch = document.querySelector('#dark-mode-button input[type="checkbox"]');
const currentTheme = localStorage.getItem('theme');

if (currentTheme) {
    document.documentElement.setAttribute('data-theme', currentTheme);
    if (currentTheme === 'dark') {
            toggleSwitch.checked = true;
    }
}

function switchTheme(e) {
    if (e.target.checked) {
        document.documentElement.setAttribute('data-theme', 'dark');
        localStorage.setItem('theme', 'dark');
    }else {        
        document.documentElement.setAttribute('data-theme', 'light');
        localStorage.setItem('theme', 'light');
    }    
}

toggleSwitch.addEventListener('change', switchTheme, false);  
:root {
  --primary-color: #495057;
  --bg-color-primary: #F5F5F5;
}

body{
  background-color: var(--bg-color-primary); 
}

[data-theme="dark"] {
  --primary-color: #8899A6;
  --bg-color-primary: #15202B;
}

table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
  background-color: #fff;
}

td, th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}
<div id="dark-mode-button">
    <input id="chck" type="checkbox">Dark Mode
    <label for="chck" class="check-trail">
      <span class="check-handler"></span>
    </label>
</div>

<table class="table">
    <thead>
      <tr>
          <th>Header 1</th>
          <th>Header 2</th>
          <th>Header 3</th>
      </tr>
    </thead>  
    <tbody>
      <tr>
        <td>Alfreds Futterkiste</td>
        <td>Maria Anders</td>
        <td>Germany</td>
      </tr>
    </tbody>                     
</table>

Share Improve this question edited Apr 9, 2022 at 8:31 Anurag Srivastava 14.4k4 gold badges36 silver badges45 bronze badges asked Jul 22, 2020 at 11:44 ZakZak 9404 gold badges20 silver badges44 bronze badges 8
  • 3 Its most likely because the JS hasnt loaded and executed the check yet. So the page loads in normal mode, JS initialises, sets the dark mode and then it changes. – Emre Koc Commented Jul 22, 2020 at 11:47
  • 1 What @EmreKoc says is correct. I would recommend putting the theme detection script as one of the first things on your page. Other than that, don't reload the whole page on navigation - just replace the part that's changed with ajax requests. – MauriceNino Commented Jul 22, 2020 at 11:49
  • Guys, sorry but can you please tell me which part of the code I should separate and where should I put that ? – Zak Commented Jul 22, 2020 at 11:51
  • 3 Move the part of the code that sets the dark-light mode in a render-blocking fashion - meaning, inside the <head> of your document. Place the remaining scripts as usual, right before the closing </body> tag. That way, the browser will stop to interpret your JS inside head and will assign the needed data-theme attribute to your <html> tag. – Roko C. Buljan Commented Jul 22, 2020 at 11:59
  • 2 @RokoC.Buljan is correct. But, consider your querySelector you may need event delegation if you are to prioritize the loading of this script – 95faf8e76605e973 Commented Jul 22, 2020 at 12:06
 |  Show 3 more comments

1 Answer 1

Reset to default 23

It would be ideal to block the page rendering by placing a small <script> tag inside the <head> of your Document. By doing so the DOM parser should stop and call the JavaScript interpreter, assign the data-theme attribute to <html> and then continue where left.

Place this <script> inside <head> - even before the <link> or <style> tags:

<head>
  <script>
    // IMPORTANT: set this in <HEAD> top before any other tag.
    const setTheme = (theme) => {
      theme ??= localStorage.theme || "light";
      document.documentElement.dataset.theme = theme;
      localStorage.theme = theme;
    };
    setTheme();
  </script>

  <!-- meta, title, etc... -->
  <!-- link, style, etc... -->
</head>

Then, right before the closing </body> tag use all the other scripts in a non-render-blocking manner:

<script src="js/index.js"></script>
<!-- other <script> tags here -->
<!-- Closing </body> </html> goes here -->

and inside your i.e: js/index.js file use:

const elToggleTheme = document.querySelector('#dark-mode-button input[type="checkbox"]');

elToggleTheme.checked = localStorage.theme === "dark";

elToggleTheme.addEventListener("change", () => {
  const theme = elToggleTheme.checked ? "dark" : "light";
  setTheme(theme);
});

PS: You can also place the above JavaScript code into your .js file if you want to keep all your scripts in one place.

If you need a creative idea for a Dark theme mode toggle checkbox see this answer: Toggle theme button

本文标签: javascriptDark mode flickers a white background for a millisecond on reloadStack Overflow