1

I made a simple slideshow using only CSS, where on the radio button click, the margin of the element changes so it displays the desired slide. You can see how it works in the code snippet below. I also made this slideshow auto play with JavaScript. The script checks the next radio button in the list every 3 seconds.

Now I need help to make the slideshow auto play in a loop and to also stop auto play when you check any radio button manually.

$(document).ready(function() {

  function autoplay() {
$("input[name=radio-button]:checked").nextAll(':radio:first').prop('checked', true);
  };
  
  setInterval(autoplay, 3000);

});
body {
margin: 0;
padding: 0;
}

.slider {
  width: 300%;
  transition: margin 1s;
  height: 100px;
}

#radio-button1:checked ~ .slider {
  margin-left: 0%;
}

#radio-button2:checked ~ .slider {
  margin-left: -100%;
}

#radio-button3:checked ~ .slider {
  margin-left: -200%;
}

.slider-item {
  float: left;
  width: 33.33%;
  height: 100%;
}
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<input type="radio" name="radio-button" id="radio-button1" checked />
<input type="radio" name="radio-button" id="radio-button2" />
<input type="radio" name="radio-button" id="radio-button3" />

<div class="slider">
  <div class="slider-item" style="background: #F00"></div>
  <div class="slider-item" style="background: #0F0"></div>
  <div class="slider-item" style="background: #00F"></div>
</div>
Katarina Perović
  • 361
  • 1
  • 5
  • 22
  • Does this answer your question? [javascript slider not slide automatically](https://stackoverflow.com/questions/38091552/javascript-slider-not-slide-automatically) – disinfor Mar 12 '20 at 19:26
  • You don't need a loop for this as it blocks the browser from rendering, just set up a function that checks the next `radioButton` and call it inside `setInterval` – Rainbow Mar 12 '20 at 19:28
  • @disinfor No, because I don't have next and previous buttons for this slider. – Katarina Perović Mar 12 '20 at 19:30
  • Marking another post as a duplicate does not mean it is an exact copy of your question, but rather very similar with what you are asking. Often it requires you to read the duplicate and adapt your code accordingly. – disinfor Mar 12 '20 at 19:36
  • @disinfor I read the post already, even before you suggested it. If it could have helped me, I wouldn't have posted this one. – Katarina Perović Mar 12 '20 at 19:40
  • @ZohirSalak Thank you so much, that's exactly what I needed, now it works, but not in a loop. I will post the new code now so hopefully someone could help me make it work in a loop. – Katarina Perović Mar 12 '20 at 20:03
  • Now all you need is to have a condition that checks when you hit the last item it resets, you can use a variable which you increment each time the function is called, then reset it when it hit 3 – Rainbow Mar 12 '20 at 21:04

2 Answers2

3

Create a curr variable, increment it inside the interval and use Modulo % to loop it back to 0:

jQuery(function($) { // DOM ready and $ alias in scope

  var curr = 0;

  function autoplay() {
    curr = ++curr % 3; // Increment and Reset to 0 when 3
    $("[name=radio-button]")[curr].checked = true;
  }

  setInterval(autoplay, 3000);

});
body {
  margin: 0;
  padding: 0;
}

.slider {
  width: 300%;
  transition: margin 1s;
  height: 100px;
}

#radio-button1:checked~.slider {
  margin-left: 0%;
}

#radio-button2:checked~.slider {
  margin-left: -100%;
}

#radio-button3:checked~.slider {
  margin-left: -200%;
}

.slider-item {
  float: left;
  width: 33.33%;
  height: 100%;
}
<input type="radio" name="radio-button" id="radio-button1" checked />
<input type="radio" name="radio-button" id="radio-button2" />
<input type="radio" name="radio-button" id="radio-button3" />

<div class="slider">
  <div class="slider-item" style="background: #F00"></div>
  <div class="slider-item" style="background: #0F0"></div>
  <div class="slider-item" style="background: #00F"></div>
</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

The better way:

  • Overflow a super parent so you don't get scrollbars
  • Use display: flex; instead of ugly floats.
  • Use $('.slider').each( so you can have multiple sliders in a single page!
  • Create a anim() play() and stop() function to control what happens.
  • Animate using transform: and transition, since transition is GPU accelerated. Whilst margins are not, and require reflow and repaint.
  • Animate the transform by using curr * -100 %
  • Use curr %= tot (Modulo operator) to loopback the curr index to 0 when needed.
  • Why create buttons manually? Create your slides manually and let JS create the buttons for you.
  • Use setInterval by storing it into a variable itv
  • To stop your auto-animation use clearInterval(itv)
  • Use .hover(stop, play) to stop on mouseenter and autoplay on mouseleave

$('.slider').each(function(slider_idx) { // Use each, so you can have multiple sliders

  let curr = 0;             // Set current index
  let itv = null;           // The interval holder
  
  const $slider = $(this);
  const $nav    = $('.slider-nav', $slider);
  const $items  = $('.slider-items', $slider);
  const $item   = $('.slider-item', $slider);
  const tot = $item.length; // How many
  const btns = [...new Array(tot)].map((_, i) => $('<input>', {
    type: 'radio',
    name: `slider-btn-${slider_idx}`,
    checked: curr == i,
    change() { // On button change event
      curr = i; // Set current index to this button index
      anim();
    }
  })[0]);
    
  function anim() {
    $items.css({transform: `translateX(-${curr*100}%)`}); // Animate
    btns[curr].checked = true; // Change nav btn state
  }

  function play() {
    itv = setInterval(() => {
      curr = ++curr % tot; // increment curr and reset to 0 if exceeds tot
      anim(); // and animate!
    }, 3000); // Do every 3sec
  }
  
  function stop() {
    clearInterval(itv);
  }

  $nav.empty().append(btns);   // Insert buttons
  $slider.hover(stop, play);   // Handle hover state
  play();                      // Start autoplay!

});
/*QuickReset*/ * {margin: 0; box-sizing: border-box;}

.slider {
  position: relative;
  height: 100px;
}

.slider-overflow {
  overflow: hidden; /* use an overflow parent! You don't want scrollbars */
  height: inherit;
}

.slider-items {
  display: flex; /* Use flex */
  flex-flow: row nowrap;
  height: inherit;
  transition: transform 1s; /* Don't use margin, use transform */
}

.slider-item {
  flex: 0 0 100%;
  height: inherit;
}

.slider-nav {
  position: absolute;
  width: 100%;
  bottom: 0;
  text-align: center;
}
<div class="slider">

  <div class="slider-overflow">
    <div class="slider-items"> <!-- This one will transition -->
      <div class="slider-item" style="background:#0bf;">curr = 0</div>
      <div class="slider-item" style="background:#fb0;">curr = 1</div>
      <div class="slider-item" style="background:#f0b;">curr = 2</div>
    </div>
  </div>
  
  <div class="slider-nav">
    <!-- JS will populate buttons here -->
  </div>
  
</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

So both DIV elements .slider-nav and .slider-overflow need to be inside of the common parent .slider - so that we properly stop (pause) the auto-animation after any interaction with those elements.

Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • +1 i tried to avoid modifying the original code too much in my answer, but i recommend the techniques used in this example over those used in my answer. – Woodrow Barlow Mar 12 '20 at 21:54
  • @Roko C. Buljan This is amazing, I have no words, I spent so many days trying to make a simple slider but wasn't able to do so since I don't know JS! I totally forgot about the hover, you even implemented that as well, it's so useful. Everything works like a charm! Just one thing - I would like to be able to customize the radio buttons since it's so important for the design I'm making. Previously, I did it using labels for radio buttons, but removed them in my code snippet to make it shorter. So, is there a way to quickly modify the JS code so I could be able to stylize the labels as buttons? – Katarina Perović Mar 12 '20 at 23:15
  • Btw, I used the second solution you provided, it's much better way of course! I don't know how to thank you enough.... – Katarina Perović Mar 12 '20 at 23:22
  • You could implement this for example: https://stackoverflow.com/a/17541916/383904 – Roko C. Buljan Mar 12 '20 at 23:27
  • @RokoC.Buljan After 3 hours of trying, I finally managed to add a label for every radio button using JS. Here's the code: `function addLabels() { var radioButton = document.getElementsByTagName("input"); for (var i = 0; i < radioButton.length; i++) { var label = ""; $(label).insertAfter(radioButton[i]); } } addLabels();` Then I stylized those labels as buttons in CSS and hid the radio buttons. For this code to work, I had to add a line in your JS code within const "btns": `id: 'radio-button'+[i],` – Katarina Perović Mar 13 '20 at 02:49
2

Looping the Slideshow

To loop the slideshow, you'll want to modify your autoplay function to:

  • Get the list of all radio buttons,
  • Determine which one is checked,
  • Uncheck that and check the next one in the list,
    • Paying special attention to wrap back to the first one if already at the end.

I'll rename the function next.

function next() {
  // get all the radio buttons.
  var buttons = $('input[name="radio-button"]');

  // look at each one in the list.
  for (let i = 0; i < buttons.length; i++) {

    // if this one isn't the checked one, move on to the next.
    if (!buttons[i].checked) {
      continue;
    }

    // okay, so this one is checked. let's uncheck it.
    buttons[i].checked = false;

    // was this one at the end of the list?
    if (i == buttons.length - 1) {
      // if so, the new checked one should be the first one.
      buttons[0].checked = true;
    } else {
      // otherwise, just the new checked one is the next in the list.
      buttons[i + 1].checked = true;
    }

    // now that we've made that change, we can break out of the loop.
    break;
  }
}

As a bonus, you can easily make a similar function called prev to go in the opposite direction.

Stopping the Slideshow

When you manually click a radio button, the slideshow should stop. To stop the slideshow, you need to clear the interval that you already set.

The .setInterval() function returns an "interval id". This id can be used to make changes to the interval later on -- like stopping it.

var timer = setInterval(next, 3000);

Then, later on, you'll want to pass that timer value back into clearInterval to stop the timer.

clearInterval(timer);

It would be easier to factor that into two functions, start and stop and let the timer value be global:

var timer;

function start() {
  timer = setInterval(next, 3000);
}

function stop() {
  clearInterval(timer);
}

So now you can call the stop function whenever any of the radio buttons receive a click event:

$('input[name="radio-button"]').on('click', stop);

Full Example

This is your code with the modifications described above.

I've added buttons for "start", "stop", "prev", and "next" -- these aren't necessary for things to function. They're just there for demonstration purposes.

$(document).ready(function() {
  /* the list of buttons is not dynamic, so rather than fetching the list of
   * buttons every time `next` or `prev` gets executed, we can just fetch it
   * once and store it globally. */
  var buttons = $('input[name="radio-button"]');
  var timer;
  
  function start() {
    timer = setInterval(next, 3000);
  }
  
  function stop() {
    clearInterval(timer);
  }

  function next() {
    for (let i = 0; i < buttons.length; i++) {
      if (!buttons[i].checked) {
        continue;
      }
      buttons[i].checked = false;
      if (i == buttons.length - 1) {
        buttons[0].checked = true;
      } else {
        buttons[i + 1].checked = true;
      }
      break;
    }
  }
  
  function prev() {
    for (let i = 0; i < buttons.length; i++) {
      if (!buttons[i].checked) {
        continue;
      }
      buttons[i].checked = false;
      if (i == 0) {
        buttons[buttons.length - 1].checked = true;
      } else {
        buttons[i - 1].checked = true;
      }
      break;
    }
  }
  
  start();
  
  buttons.on('click', stop);
  
  /* these next lines are unnecessary if you aren't including the buttons */
  $('#start').on('click', start);
  $('#stop').on('click', stop);
  $('#next').on('click', next);
  $('#prev').on('click', prev);
});
body {
  margin: 0;
  padding: 0;
}

.slider {
  width: 300%;
  transition: margin 1s;
  height: 100px;
}

#radio-button1:checked~.slider {
  margin-left: 0%;
}

#radio-button2:checked~.slider {
  margin-left: -100%;
}

#radio-button3:checked~.slider {
  margin-left: -200%;
}

.slider-item {
  float: left;
  width: 33.33%;
  height: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<input type="radio" name="radio-button" id="radio-button1" checked />
<input type="radio" name="radio-button" id="radio-button2" />
<input type="radio" name="radio-button" id="radio-button3" />

<div class="slider">
  <div class="slider-item" style="background: #F00"></div>
  <div class="slider-item" style="background: #0F0"></div>
  <div class="slider-item" style="background: #00F"></div>
</div>

<!-- these buttons are unnecessary, they are only for demonstration purposes -->
<button id="start">Start</button>
<button id="stop">Stop</button>
<button id="prev">Prev</button>
<button id="next">Next</button>
Woodrow Barlow
  • 8,477
  • 3
  • 48
  • 86
  • Why not cache `var buttons`? – Roko C. Buljan Mar 12 '20 at 21:20
  • Also, the interval is messing with user input resulting in a bad UX – Roko C. Buljan Mar 12 '20 at 21:21
  • @RokoC.Buljan i agree with both your points, i was just focusing on answering the question as asked. – Woodrow Barlow Mar 12 '20 at 21:23
  • Instead of the breaks and continues and the multitude o if statements it would be far easier to just create a `c` variable counter and simply increment and decrement that `c` on `prev / next` and set the checked state accordingly based on `eq` index . – Roko C. Buljan Mar 12 '20 at 21:25
  • Here's a totally simplistic remake of your exact code https://jsbin.com/veluqigequ/edit?html,css,js,console,output (but still misses the UX improvements) – Roko C. Buljan Mar 12 '20 at 21:32
  • i modified the example to cache the buttons. thank you for the suggestion. – Woodrow Barlow Mar 12 '20 at 21:58
  • @WoodrowBarlow Thank you so much for your time and answer! Reading the Roko's answer, I see there's a much more professional and better way than the one I imagined, but you did a great job making my code work and trying to avoid modifying it too much. – Katarina Perović Mar 12 '20 at 23:21