When class is toggled on `html` change Swiper JS settings

I have a Swiper JS instance which works as expected. However this page has a toggle which adds classes to the html element which in turn changes the appearance of the page with CSS.

When the class retro is added. I want to change how the Swiper JS behaves. Mainly disabling ‘loop’, ‘autoplay’ and changing the effect from ‘fade’ to ‘loop’.

I’ve done this using:

const hasClassRetro = document.documentElement.classList.contains('retro');

Then on the settings effect: hasClassRetro ? 'slide' : 'fade' for example. Which seems to get the desired result when the class is/is not on html on page load.

I was wondering is there away to check if there is a class on html when the toggle is clicked and if that is equal to retro update the Swiper instance?

I have an example on this JS Fiddle: https://jsfiddle.net/mvotqxbr/

You can see currently, if you update <html class=""> to <html class="retro"> the settings change after running again. I need to get it working when clicking “Toggle Classes” and 'retro is added.

Hope someone can help with this! :slight_smile:

Not really my area but I think this SO post has an answer of sorts.

e.g. Something very roughly like this: :slight_smile:



/* CLASS 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('fade', 'true');
    swiper.slideTo(slideIndex, 0);

  } else if (html.classList.contains('retro')) {
    html.classList.remove('retro');
    let slideIndex = swiper.activeIndex;
    swiper.destroy(true, true);
    swiper = initSwiper('slide', 'false');
    swiper.slideTo(slideIndex, 0);
  } else {
    html.classList.add('dark-mode');
  }

});


/* CAROUSEL */

var caption = document.querySelector(".swiper-caption");
const effect = 'fade';
const type = 'true';
let swiper = initSwiper(effect);

function initSwiper(effect, type) {
  return new Swiper(".swiper", {
    // Disable preloading of all images
    preloadImages: false,
    observer: true,
    // Enable lazy loading
    lazy: true,
    effect: effect,
    fadeEffect: {
      crossFade: true
    },
    loop: type,
    autoplay: {
      delay: 1200,
      disableOnInteraction: false,
      pauseOnMouseEnter: true
    },
    navigation: {
      nextEl: ".swiper-button-next",
      prevEl: ".swiper-button-prev"
    },
    pagination: {
      el: ".swiper-pagination",
      type: "fraction"
    },
    on: {
      init: function() {
        updateCaptionText(this);
      },
      activeIndexChange: function() {
        updateCaptionText(this);
      }
    }
  });
}
1 Like

Thanks for this! That definitely updates the settings on the fly, I’ve forked the JS Fiddle here: https://jsfiddle.net/fe2L9xq8/2/

Currently it doesn’t change any settings until you’ve cycled through the settings once. Then it looks like the slides when it should fade and vice versa - which I will be easy to swap around.

Unless that’s correct and it’s the fact you need to click through each state/class first before that causes them to be the wrong way around? Currently having a play around with this! :slight_smile:

It does change straight away for me except that you have a dark-mode toggle that does nothing except change the background. That means you have to make 2 clicks before you see a change.

If you only wanted two states then the logic is different but I’m not sure what you were aiming for,

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', 'true');
    swiper.slideTo(slideIndex, 0);

  } else  {
    html.classList.remove('retro');
    html.classList.add('dark-mode');
    let slideIndex = swiper.activeIndex;
    swiper.destroy(true, true);
    swiper = initSwiper('fade', 'false');
    swiper.slideTo(slideIndex, 0);
  } 
});

If you wanted 3 states then you’d need to initialise the dark-mode state (the last ‘else’ in your code) with the action you want like the other two items. At the moment it just adds a class to the html. e.g. you have this which just adds a class but doesn’t change the swiper.

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

Sorry I’ll add a bit of context outside of the example! So the 3 states I have on my actual page are:

  • Default
  • Dark (just inverted colours)
  • Retro (completely different UI, like old mac OS)

I simplified it on JS Fiddle and added coloured backgrounds to be able to tell the classes on HTML were changing.

So you’re right Default + Dark states both use exactly the same settings and the base Swiper JS settings. Only Retro has the modified settings so turning off autoplay, loop (need to figure out how to do autoplay) and changing the effect to slide.

But on the example I posted. Nothing updated when you click through each state (so 3 times) but when you return o the start the Default (no class) state changes to slide, as does Dark, then Retro changes to fade - do you not get that? :thinking:

No it changes instantly for me. Except for the dark-mode as that does nothing to the swiper. That’s why you need two clicks each time.

If I’m on slide2 and click it changes instantly to the new version (there will be a slight flicker).

I think you are confused as not every click will change to the swiper as you just change background on dark-mode and set fade on the first click (although that was just the way I added it to the code).

For example if you start from the beginning and click the toggle text once then nothing happens to the slider but the backgrounds turns yellow. If you click again then a new slider is initialised but the first one is fade which is the default so you see nothing much change. If you click a third time it changes instantly to the slide. If you click again then nothing happens again as the dark-mode class is added and the last swiper version just continues. If you reverse the slide and fade values in the js then you see the change quicker but of course never on dark-mode.

You only have three slides anyway so it’s quite hard to test :slight_smile:

The first click will never do anything other than change the background. If you want it to slide on dark-mode then you need the code I gave in post #4.

Ah right! I’ve included a video what I’m seeing, I wonder if it’s a mac/Chrome issue. Can’t upload the video but I’ve put it here: https://filebin.net/p2hqxvu89oge93wo

…just to check I’m not losing my mind haha

I am on Chrome/ Mac :slight_smile:

It’s hard to tell from the video as I can’t tell when you click but it seems to be behaving exactly like I described above.

When you click first nothing happens,. When you click second nothing really happens as its the same action. When you click a third time it changes instantly because that’s the slide version. If you click again nothing happens because you are back in dark-mode again and no change to the swiper.

Try the code in post #4 as that has only 2 states and they will change straight away. (Maybe add more slides for testing.)

To change the slide you just change the argument on the js.

e.g.

swiper = initSwiper('fade', 'true');

‘fade’ is passed into the slider so if you change that to ‘slide’ it will start the slide. The true value is for the loop and sets it to true. It probably should not have quotes around it.

You can do the same for the autoplay and just pass a large value for the delay so it never happens.

If you put that all together you get this.

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('fade', true, 999999);/* this will fade and not autoplay*/
    swiper.slideTo(slideIndex, 0);

  } else if (html.classList.contains('retro')) {
    html.classList.remove('retro');
    let slideIndex = swiper.activeIndex;
    swiper.destroy(true, true);
    swiper = initSwiper('slide', false, 2000);/* this will slide, it won't loop and will autoplay*/
    swiper.slideTo(slideIndex, 0);
  } else {
    html.classList.add('dark-mode');/* this changes nothing with the slider. The lae slider will stay activated*/
  }
});




/* CAROUSEL */

var caption = document.querySelector(".swiper-caption");
const effect = 'fade';
const type = true;
var myDelay = 1200;
let swiper = initSwiper(effect,type,myDelay);

function initSwiper(effect, type, myDelay) {
  return new Swiper(".swiper", {
    // Disable preloading of all images
    preloadImages: false,
    observer: true,
    // Enable lazy loading
    lazy: true,
    effect: effect,
    fadeEffect: {
      crossFade: true
    },
    loop: type,
    autoplay: {
      delay: myDelay,
      disableOnInteraction: false,
      pauseOnMouseEnter: true
    },
    navigation: {
      nextEl: ".swiper-button-next",
      prevEl: ".swiper-button-prev"
    },
    pagination: {
      el: ".swiper-pagination",
      type: "fraction"
    },
    on: {
      init: function() {
        updateCaptionText(this);
      },
      activeIndexChange: function() {
        updateCaptionText(this);
      }
    }
  });
}

So the code above allows you to change from slide to fade and stop or start the autoplay and loop values. You just need to put them where you want then to happen.

That still won’t change your logic. At the moment dark-mode does nothing. When you go back to dark-mode the swiper will stay in whatever state it was in before you toggled the class. If you wanted dark-mode to be a fade (or a slide) then you need to reinitialise it in that last else statement as with the ones before.

I put it into a pen.

The dark mode has the swiper static and you need to click the image to move to the next.

The next toggle starts the slider.

The next toggle goes back to fade but with autoplay.

Even if those aren’t the behaviours you need you should be able to change each as you want :slight_smile:

1 Like

Definitely mate! Really appreciate it just wanted to check we were seeing the same thing and what browser you were using so I could make sure we meant the same thing! :smiley:

But yeah default/initial (no class) and dark mode should be the same. Just retro is different so I’ll have a play around! In my video nothing changes at all the first 3 clicks (state changes). Then when it gets back to the start (4th click) it slides for that + dark and then fades for ‘retro’. So was the other way around - but the CodePen looks fine so maybe an issue with my JS Fiddle!

1 Like

Just a small bit of input. You have classList.replace. If you read that link you will see it returns a boolean value.

Return Value

A boolean value, which is true if oldToken was successfully replaced, or false if not.

So you could replace this

if (html.classList.contains("dark-mode")) {
    html.classList.remove("dark-mode");
    html.classList.add("retro");

with

if (html.classList.replace('dark-mode', 'retro')) {

}
2 Likes

On thing I neglected with Swiper JS is that there is no setting for autoplay: false for example. Just specifying autoplay {} with the brackets activates it and there isn’t a setting within that to disable.

Are there any drawbacks to just setting a really, really high value like 9999999? So you basically never see it?

Just forked @PaulOB’s CodePen: https://codepen.io/moy/pen/NWzXaPJ

No real changes but removed all the settings for dark-mode so it’s basically just a duplicate (same settings) as the default/no class state.

Yes that was a bit of a hack of mine but couldn’t see a way to disable it completely. Maybe @rpg_digital could figure out how to do it properly :slight_smile:

1 Like

That solution works fine for me and for what I need this for - I think it’s a good idea! :slight_smile:

I can’t see a way without somehow replacing the whole autoplay {} bit. Reading the documentation maybe I can just add stopOnLastSlide to stop it scrolling back through the slides and add another const for that and set true/false where needed.

1 Like