0

Making a sliding button to switch website theme using a CSS variable and javascript. It is working properly except there is a small bug that I am unable to fix. If I reload the page is light theme, the functionality of the button is being reversed. The "On" state of the button turns on light mode and off state toggles dark mode. However, the initial configuration is entirely opposite.

As you can see in the executable code snippet below, I tried solving this using click() function. This problem arises only when the value of num is 0 and page is reloaded. So, I thought if I store a variable in localStorage as false and check its value at the beginning of the function and if its false, then click the button and dont execute function, if its not false, then execute normally.

But it is not working for some reason. Please check this code:

if (!localStorage.getItem('thisvarisgud4me')) {
    localStorage.setItem("thisvarisgud4me", "1")
}

document.getElementById("btn").addEventListener("click", change);
var c = "true";
if (!localStorage.getItem("clickc"))
{
    localStorage.setItem("clickc", c);
}

function change() {
    if (localStorage.getItem("clickc") == "false") {
        localStorage.setItem("clickc","true");
        document.getElementById("btn").click();
    }
    else if (localStorage.getItem("clickc") == "true") {
        if (localStorage.getItem('thisvarisgud4me') == '1') {
            localStorage.setItem("thisvarisgud4me", '0')
        } else {
            localStorage.setItem("thisvarisgud4me", '1')
        }

        var num = Number(localStorage.getItem('thisvarisgud4me'));
        let root = document.documentElement;
        root.style.setProperty("--numvar", num);
        console.log(num);
        if (num == 0) {
            window.addEventListener("beforeunload", function (event) {
                console.log("The page is redirecting")
                alert("Reload");
                localStorage.setItem("clickc", "false");
                // document.getElementById("btn").click();
                // debugger;
            });
        }
    }
}
var num = Number(localStorage.getItem('thisvarisgud4me'));
let root = document.documentElement;
root.style.setProperty("--numvar", num);
:root {
    --numvar: 0;
}

html {
    filter: invert(var(--numvar));
}


body {
    background: #fff;
}

.outer-button {
    display: inline-block;
    height: 28px;
    width: 28px;
    position: relative;
    margin: 0 3px;
}

.inner-button {
    display: inline-block;
    position: absolute;
    width: 100%;
    height: 100%;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), inset 0px 0px 1px 2px white;
    border-radius: 20px;
    background: #f5f5f5;
}

span {
    display: inline-block;
    vertical-align: middle;
}

.status-text {
    color: white;
    text-transform: uppercase;
    font-size: 11px;
    font-family: Futura, sans-serif;
    transition: all 0.2s ease;
}

.sliding-switch {
    height: 28px;
    width: 72px;
    position: relative;
}

.outer-switch-box {
    overflow: hidden;

    height: 100%;
    width: 100%;
    display: inline-block;
    border-radius: 20px;
    position: relative;
    box-shadow: inset 0 1px 3px 0px #818181, 0px 1px 2px 1px white;
    transition: all 0.3s ease;
    transition-delay: 65ms;
    position: absolute;
    z-index: 1;
}

.inner-switch-box {
    position: relative;
    width: 175px;
    transition: all 0.3s ease;
}

/* .switch-checkbox:checked+.outer-switch-box .unchecked-text {
    color: transparent;
}

.switch-checkbox:not(:checked)+.outer-switch-box .checked-text {
    color: transparent;
} */

.switch-checkbox:checked+.outer-switch-box .inner-switch-box {
    left: -27px;
    /*OFF*/
}

.switch-checkbox:not(:checked)+.outer-switch-box .inner-switch-box {
    left: 20px;
    /*ON*/
}

.switch-checkbox:checked+.outer-switch-box {
    /* background-image: linear-gradient(#b6d284, #b6d284); */
    background: #492d7b;
    /* background: #b6d284; */
}

.switch-checkbox:not(:checked)+.outer-switch-box {
    /* background-image: linear-gradient(#cbcbcb, #dbdbdb); */
    background: #dbdbdb;
}

[type="checkbox"] {
    margin: 0;
    padding: 0;
    appearance: none;
    width: 100%;
    height: 100%;
    border: 1px solid black;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 100;
    opacity: 0;
}

.unchecked-text {
    color: black !important;
    font-weight: 700;
}

.btn-heading {
    color: black;
    font-family: 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Open Sans', 'Helvetica Neue', 'sans-serif';
    padding: .4vw 0;
}

body {
    float: left;
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
   
<body>
    <div class="btn-heading">Dark Mode</div>
    <div class="sliding-switch">
        <input type="checkbox" id="btn" class="switch-checkbox" />
        <div class="outer-switch-box">
            <div class="inner-switch-box">
                <span class="status-text checked-text" id="textp1">on</span>
                <span class="outer-button">
                    <span class="inner-button"></span>
                </span>
                <span class="status-text unchecked-text" id="textp2">off</span>
            </div>
        </div>
    </div>
</body>

</html>

As you might have noticed, I also tried manipulating CSS pseudo class properties using JS. But that was a complete mess. Then, I thought of this approach and I was quite confident that it is correct but looks like I was wrong :(

4
  • 2
    Whenever I come to code like this the first thing I do is start removing code until it starts breaking. I also set standards on how things happen. For instance, I always use getItem and setItem to interface with localStorage; I never use localStorage.<key>. Also, I almost always create functions that get or set localStorage values, coercing or serializing/deserializing them to the correct types as needed. That way I'm always dealing with the "correct" types. Typically, I'll use local variables to hold state and only transfer to localStorage at specific times. Commented Jul 14, 2021 at 20:23
  • Ok @HereticMonkey , I didn't think about it that way. Actually, today is the first time I'm working with localStorage and I'm also at a beignning stage in JS. I have changed localStorage.key to set and get functions and I would appreciate it if you could help me fix the bug in the code. I will definitely keep these things in mind and write a better code from next time. In my code, I need to click the button if user reloads the page in light mode(num=0) after reload is done. So, the only way that I know about to implement this is a localStorage variable. I have no experience with backend at all Commented Jul 14, 2021 at 20:35
  • Basically, the reason for the "switcheroo" is the lines starting with if (localStorage.getItem('thisvarisgud4me') == '1') {. Those will always switch the '0' to '1' every time clickc is "true", and clickc is "true" every time the page is loaded (since you set it unconditionally in the first few lines of code). Commented Jul 14, 2021 at 20:42
  • I wrote the code properly this time but it is behaviour in a wierd manner Commented Jul 14, 2021 at 21:00

1 Answer 1

1

Just adding a condition to setting "clickc" to "true" will probably do the trick. Here I've used a similar condition to that you've already used for the "thisvarisgud4me" key.

I took the opportunity to test out a utility I created that essentially implements the Storage API (that's what <script src="https://heretic-monkey.link/FauxStorage.js"></script> is in the HTML, and why all of your localStorage references now say localStore).

So if you decide to copy and paste this into your own code, just do a search and replace of localStore with localStorage.

if (!localStore.getItem('thisvarisgud4me')) {
  localStore.setItem("thisvarisgud4me", "1")
}

document.getElementById("btn").addEventListener("click", change);
var c = "true";
if (!localStore.getItem("clickc")) {
  localStore.setItem("clickc", c);
}

function change() {
  if (localStore.getItem("clickc") == "false") {
    document.getElementById("btn").click();
    localStore.getItem("clickc") = "true";
  } else if (localStore.getItem("clickc") == "true") {
    if (localStore.getItem('thisvarisgud4me') == '1') {
      localStore.setItem("thisvarisgud4me", '0')
    } else {
      localStore.setItem("thisvarisgud4me", '1')
    }

    var num = Number(localStore.getItem('thisvarisgud4me'));
    let root = document.documentElement;
    root.style.setProperty("--numvar", num);
    console.log(num);
    if (num == 0) {
      window.addEventListener("beforeunload", function(event) {
        console.log("The page is redirecting")
        alert("Reload");
        localStore.setItem("clickc", "false");
        // document.getElementById("btn").click();
        // debugger;
      });
    }
  }
}
var num = Number(localStore.getItem('thisvarisgud4me'));
let root = document.documentElement;
root.style.setProperty("--numvar", num);
:root {
  --numvar: 0;
}

html {
  filter: invert(var(--numvar));
}

body {
  background: #fff;
}

.outer-button {
  display: inline-block;
  height: 28px;
  width: 28px;
  position: relative;
  margin: 0 3px;
}

.inner-button {
  display: inline-block;
  position: absolute;
  width: 100%;
  height: 100%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), inset 0px 0px 1px 2px white;
  border-radius: 20px;
  background: #f5f5f5;
}

span {
  display: inline-block;
  vertical-align: middle;
}

.status-text {
  color: white;
  text-transform: uppercase;
  font-size: 11px;
  font-family: Futura, sans-serif;
  transition: all 0.2s ease;
}

.sliding-switch {
  height: 28px;
  width: 72px;
  position: relative;
}

.outer-switch-box {
  overflow: hidden;
  height: 100%;
  width: 100%;
  display: inline-block;
  border-radius: 20px;
  position: relative;
  box-shadow: inset 0 1px 3px 0px #818181, 0px 1px 2px 1px white;
  transition: all 0.3s ease;
  transition-delay: 65ms;
  position: absolute;
  z-index: 1;
}

.inner-switch-box {
  position: relative;
  width: 175px;
  transition: all 0.3s ease;
}


/* .switch-checkbox:checked+.outer-switch-box .unchecked-text {
    color: transparent;
}

.switch-checkbox:not(:checked)+.outer-switch-box .checked-text {
    color: transparent;
} */

.switch-checkbox:checked+.outer-switch-box .inner-switch-box {
  left: -27px;
  /*OFF*/
}

.switch-checkbox:not(:checked)+.outer-switch-box .inner-switch-box {
  left: 20px;
  /*ON*/
}

.switch-checkbox:checked+.outer-switch-box {
  /* background-image: linear-gradient(#b6d284, #b6d284); */
  background: #492d7b;
  /* background: #b6d284; */
}

.switch-checkbox:not(:checked)+.outer-switch-box {
  /* background-image: linear-gradient(#cbcbcb, #dbdbdb); */
  background: #dbdbdb;
}

[type="checkbox"] {
  margin: 0;
  padding: 0;
  appearance: none;
  width: 100%;
  height: 100%;
  border: 1px solid black;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 100;
  opacity: 0;
}

.unchecked-text {
  color: black !important;
  font-weight: 700;
}

.btn-heading {
  color: black;
  font-family: 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Open Sans', 'Helvetica Neue', 'sans-serif';
  padding: .4vw 0;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://heretic-monkey.link/FauxStorage.js"></script>
</head>
  <body>
    <div class="btn-heading">Dark Mode</div>
    <div class="sliding-switch">
      <input type="checkbox" id="btn" class="switch-checkbox" />
      <div class="outer-switch-box">
        <div class="inner-switch-box">
          <span class="status-text checked-text" id="textp1">on</span>
          <span class="outer-button">
                    <span class="inner-button"></span>
          </span>
          <span class="status-text unchecked-text" id="textp2">off</span>
        </div>
      </div>
    </div>
  </body>

</html>

Here's how I would refactor it. This is more of an object-oriented way of doing things; it might not appeal to everyone and it certainly isn't meant to. It works for me and I'm the only one I need to make happy with it :).

class ThemeStore {
  _darkModeKey = "thisvarisgud4me";
  _darkMode = null;
  get darkMode() {
    if (this._darkMode === null) {
      if (!localStore.getItem(this._darkModeKey)) {
        localStore.setItem(this._darkModeKey, 0);
      }
      this._darkMode = JSON.parse(localStore.getItem(this._darkModeKey));
    }
    return this._darkMode;
  }
  set darkMode(value) {
    this._darkMode = value;
  }
  persist() {
    localStore.setItem("thisvarisgud4me", JSON.stringify(this.darkMode));
  }
}

var themeStore = new ThemeStore();
document.getElementById("btn").addEventListener("click", change);

function change(e) {
  themeStore.darkMode = e.target.checked ? 0 : 1;
  let root = document.documentElement;
  root.style.setProperty("--numvar", themeStore.darkMode);
  console.log(themeStore.darkMode);
  if (themeStore.darkMode === 0) {
    window.addEventListener("beforeunload", function(event) {
      console.log("The page is redirecting")
      themeStore.persist();
    });
  }
}

document.getElementById("btn").dispatchEvent(new CustomEvent("change"));
:root {
  --numvar: 0;
}

html {
  filter: invert(var(--numvar));
}

body {
  background: #fff;
}

.outer-button {
  display: inline-block;
  height: 28px;
  width: 28px;
  position: relative;
  margin: 0 3px;
}

.inner-button {
  display: inline-block;
  position: absolute;
  width: 100%;
  height: 100%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4), inset 0px 0px 1px 2px white;
  border-radius: 20px;
  background: #f5f5f5;
}

span {
  display: inline-block;
  vertical-align: middle;
}

.status-text {
  color: white;
  text-transform: uppercase;
  font-size: 11px;
  font-family: Futura, sans-serif;
  transition: all 0.2s ease;
}

.sliding-switch {
  height: 28px;
  width: 72px;
  position: relative;
}

.outer-switch-box {
  overflow: hidden;
  height: 100%;
  width: 100%;
  display: inline-block;
  border-radius: 20px;
  position: relative;
  box-shadow: inset 0 1px 3px 0px #818181, 0px 1px 2px 1px white;
  transition: all 0.3s ease;
  transition-delay: 65ms;
  position: absolute;
  z-index: 1;
}

.inner-switch-box {
  position: relative;
  width: 175px;
  transition: all 0.3s ease;
}


/* .switch-checkbox:checked+.outer-switch-box .unchecked-text {
    color: transparent;
}

.switch-checkbox:not(:checked)+.outer-switch-box .checked-text {
    color: transparent;
} */

.switch-checkbox:checked+.outer-switch-box .inner-switch-box {
  left: -27px;
  /*OFF*/
}

.switch-checkbox:not(:checked)+.outer-switch-box .inner-switch-box {
  left: 20px;
  /*ON*/
}

.switch-checkbox:checked+.outer-switch-box {
  /* background-image: linear-gradient(#b6d284, #b6d284); */
  background: #492d7b;
  /* background: #b6d284; */
}

.switch-checkbox:not(:checked)+.outer-switch-box {
  /* background-image: linear-gradient(#cbcbcb, #dbdbdb); */
  background: #dbdbdb;
}

[type="checkbox"] {
  margin: 0;
  padding: 0;
  appearance: none;
  width: 100%;
  height: 100%;
  border: 1px solid black;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 100;
  opacity: 0;
}

.unchecked-text {
  color: black !important;
  font-weight: 700;
}

.btn-heading {
  color: black;
  font-family: 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Open Sans', 'Helvetica Neue', 'sans-serif';
  padding: .4vw 0;
}
<script src="https://heretic-monkey.link/FauxStorage.js"></script>
<div class="btn-heading">Dark Mode</div>
<div class="sliding-switch">
  <input type="checkbox" id="btn" class="switch-checkbox" />
  <div class="outer-switch-box">
    <div class="inner-switch-box">
      <span class="status-text checked-text" id="textp1">on</span>
      <span class="outer-button">
        <span class="inner-button"></span>
      </span>
      <span class="status-text unchecked-text" id="textp2">off</span>
    </div>
  </div>
</div>

Sign up to request clarification or add additional context in comments.

4 Comments

I can't reload to check in stackoverflow
Ok, thanks for your input. I'm done for today. Spent like 10+ hours for making a single button!
Sorry, I was playing around with refactoring some things. LocalStorage doesn't work on Stack Overflow anyway. That's why I changed it to use my FauxStorage utility, which fakes it enough so that you can see something happening, even if you don't get the full localStorage experience. Anyway, I added some code to show what my first comment was talking about. Again, doesn't work here in the snippet too well, but might be interesting to study if you have time :).
Sure but actually I was making this button to as a webpage to embed it with my real project. I made the javascript as a separate file and linked it to both this page and original page of the website. The embedded part is working fine. However, the real page doesn't seem to change at all. Do you know why it could be happening? I have used the same variable names in original page too

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.