Home > OS >  Stop running JS when class is added to HTML - Run again when class is removed
Stop running JS when class is added to HTML - Run again when class is removed

Time:01-09

I have a 'theme toggle' which changes the look/feel of my UI. There is also a custom JS cursor that I don't need on one of the themes. I thought it would be a good idea to kill the script when the relevant class is on html as there's a lot of calculation going on with the pointer position. Rather than just use display: none and leave the script running.

There are 3 modes/themes:

  • Default (always first / onl oad)
  • Dark
  • Retro - which is the one where the script should be stopped.

I tried to achieve this using the following method. To test, I add the .retro class to html so it's the first theme, it does stop the script from running and clicking the toggle turns the script on again. But it only does it once.

Obviously removing the class onl oad doesn't work either but I just wanted to do the above to see if anything was happening. Can someone tell me where I'm going wrong. What's the best approach?

Essentially what I'm trying to achieve is: Run this script, unless the class .retro is present, if so stop it. If the class .retro is removed, run the script again.

/* Toggle */

const html = document.querySelector("html");
const button = document.querySelector(".contrast__link");

button.addEventListener("click", (e) => {
    e.preventDefault();
    if (html.classList.contains("dark-mode")) {
        html.classList.remove("dark-mode");
        html.classList.add("retro");
        let slideIndex = swiper.activeIndex;
        swiper.destroy(true, true);
        swiper = initSwiper("slide", false, 999999999); // Retro: This should slide, no autoplay or loop
        swiper.slideTo(slideIndex, 0);
    } else if (html.classList.contains("retro")) {
        html.classList.remove("retro");
        let slideIndex = swiper.activeIndex;
        swiper.destroy(true, true);
        swiper = initSwiper("fade", true, 1200); // Default: This should fade, autoplay & loop
        swiper.slideTo(slideIndex, 0);
    } else {
        html.classList.add("dark-mode");
    }
});

/* Cursor */

function createHandler(callback) {
    return function(event) {
        if (!document.documentElement.classList.contains('retro')) {
            callback.call(this, event)
        }
    }
}

window.addEventListener('mousemove', createHandler(function() {

    var cursor = document.querySelector(".cursor");
    var cursorTrail = document.querySelector(".cursor-trail");
    var a = document.querySelectorAll("a");
    var timeout;

    window.addEventListener(
        "mousemove",
        function(e) {
            var x = e.clientX;
            var y = e.clientY;
            cursor.style.transform = `translate(${x - 2}px, ${y - 2}px)`;
            if (!timeout) {
                timeout = setTimeout(function() {
                    timeout = null;
                    cursorTrail.style.transform = `translate(${x - 16}px, ${y - 16}px)`;
                }, 24);
            }
        },
        false
    );

    /**
     * Add/remove classes on click (anywhere).
     */

    document.addEventListener("mousedown", function() {
        cursor.classList.add("cursor--click");
    });

    document.addEventListener("mouseup", function() {
        cursor.classList.remove("cursor--click");
    });
    
    /**
     * Add/remove set classes on hover.
     * 
     * 1. This used to start with `a.forEach((item) => {` but changed to `let` so
     *    that an additional (non-anchor) item could be targeted. `#hello` is for
     *    the image on the 404 page.
     */

    // a.forEach((item) => {
    let links = document.querySelectorAll('a, #hello'); /* [1] */
    links.forEach((item) => { /* [1] */
        item.addEventListener("mouseover", () => {
            cursorTrail.classList.add("cursor-trail--hover");
        });
        item.addEventListener("mouseleave", () => {
            cursorTrail.classList.remove("cursor-trail--hover");
        });
    });
    
    /**
     * Add custom classes on hover if the cursor needs to be manipulated in a
     * unique way. If an element has a `data-interaction=""` value set. This will
     * be added as a class to the cursor on hover. For example, this is used to
     * style the prev/next arrows on the carousel.
     *
     * This could be set using a specific class but I've just left it targeting all
     * `a` elements for now. Which will add a class of `undefined` if no dataset is
     * specified.
     */

    a.forEach((item) => {
        const interaction = item.dataset.interaction;

        item.addEventListener("mouseover", () => {
            cursor.classList.add(interaction);
        });
        item.addEventListener("mouseleave", () => {
            cursor.classList.remove(interaction);
        });
    });
}))
.contrast__link {
  background: white;
}

.cursor {
  background: red;
  height: 20px;
  width: 20px;
  position: fixed;
}

.dark-mode {
  background: black;
}

.retro {
  background: blue;
}
<a href="" >Toggle Classes</a>

<div ><span></span></div>
<div ></div>

CodePudding user response:

I would suggest to add and remove "mousemove" listener inside "click" listener, when you toggling the theme. So if user selects "retro", the event listener should be removed, and when it is another theme, the listener should be added.

CodePudding user response:

I have tried with simple CSS code, it's working

 .retro .cursor{
    display:none
}

but you want to stop the function also, so tried the following that also worked. When the theme is retro on mouse move the cursor changing function is not calling

/* Toggle */

const html = document.querySelector("html");
const cursor = document.querySelector(".cursor");

function addClass() {
  if (html.classList.contains("dark-mode")) {
    html.classList.remove("dark-mode");
    html.classList.add("retro");
    cursor.style.display = 'none'

  } else if (html.classList.contains("retro")) {
    html.classList.remove("retro");

  } else {
    html.classList.add("dark-mode");
  }
}

document.addEventListener('mousemove', function(e) {
  if (html.classList.contains("retro")) {
    cursor.style.display = 'none'
  } else {
    cursor.style.display = 'block'
    var x = e.clientX;
    var y = e.clientY;
    cursor.style.transform = `translate(${x - 2}px, ${y - 2}px)`;
  }
});
.cursor {
  background: red;
  height: 20px;
  width: 20px;
  position: fixed;
}

.dark-mode {
  background: black;
}

.retro {
  background: blue;
}
<div ></div>
<button onclick='addClass()'>Add Class</button>

CodePudding user response:

As @Mykhailo Svyrydovych suggested, to avoid any overhead of the unneeded events call, you would need to have explictly named functions for each event handler, then you could use removeEventListener when you're toggling to the retro theme, removing the handler of each mousemove, mouseenter, mouseleave, mouseup and mousedown events that you don't want to run on that theme. Of course, you would need to re-bind all the events again using the proper addEventListener calls when toggling to the other two themes.

But I think that, sometimes, the easiest approach is good enough. If you measure the performance and then discover that the events keeping to fire isn't a big concern for your case, go for it. You can easily avoid the unwanted effects of the custom cursor, inside each event handler, just by checking a simple boolean:

const html = document.documentElement;
const button = document.querySelector(".contrast__link");
const cursor = document.querySelector(".cursor");
const cursorTrail = document.querySelector(".cursor-trail");
const a = document.querySelectorAll("a");
let timeout;
let customCursor = true;

button.addEventListener("click", (e) => {
  e.preventDefault();
  if (html.classList.contains("dark-mode")) {
    html.classList.remove("dark-mode");
    html.classList.add("retro");
    customCursor = false;
    cursor.style.display = "none";
  } else if (html.classList.contains("retro")) {
    html.classList.remove("retro");
    customCursor = true;
    cursor.style.display = "block";
  } else {
    html.classList.add("dark-mode");
    customCursor = true;
    cursor.style.display = "block";
  }
});

window.addEventListener(
  "mousemove",
  function(e) {
    if (!customCursor) return;
    var x = e.clientX;
    var y = e.clientY;
    cursor.style.display = "block";
    cursor.style.transform = `translate(${x - 2}px, ${y - 2}px)`;
    if (!timeout) {
      timeout = setTimeout(function() {
        cursorTrail.style.transform = `translate(${x - 16}px, ${y - 16}px)`;
      }, 24);
    }
  },
  false
);

document.addEventListener("mousedown", () => {
  if (!customCursor) return;
  cursor.classList.add("cursor--click");
});

document.addEventListener("mouseup", () => {
  if (!customCursor) return;
  cursor.classList.remove("cursor--click");
});

let links = document.querySelectorAll('a, #hello');
links.forEach((item) => {
  item.addEventListener("mouseover", () => {
    if (!customCursor) return;
    cursorTrail.classList.add("cursor-trail--hover");
  });
  item.addEventListener("mouseleave", () => {
    if (!customCursor) return;
    cursorTrail.classList.remove("cursor-trail--hover");
  });
});

a.forEach((item) => {
  const interaction = item.dataset.interaction;
  item.addEventListener("mouseover", () => {
    if (!customCursor) return;
    cursor.classList.add(interaction);
  });
  item.addEventListener("mouseleave", () => {
    if (!customCursor) return;
    cursor.classList.remove(interaction);
  });
});
.contrast__link {
  background: white;
}

.cursor {
  background: red;
  height: 20px;
  width: 20px;
  position: fixed;
  display: none;
}

.dark-mode {
  background: black;
}

.retro {
  background: blue;
}
<a href="" >Toggle Classes</a>

<div ><span></span></div>
<div ></div>

P.S.: I removed the Swiper calls from my code sample because they appeared to be totally unrelated to the question being asked, and were causing console errors.

  • Related