Infinity Loop Slider Concepts
tl;dr - JSBin Example: http://jsbin.com/ufoceq/8/
A simple approach to create an infinite image slider without too much effort is as follows: let say for the sake of simplicity that you have <n>
images to slide in a loop, so that after the n
th image the next one to visualize is the 1
st (and vice-versa).
The idea is to create a clone of first and last image so that
- the clone of the last image is prepended before the first one;
- the clone of the first image is appended after the last one.
Whatever is the amount of your images, you will need to append at most only 2 cloned elements.
Again for the simplicity, let say that all images are 100px
wide and they're wrapped in a container that you move left/right into a clipped mask with overflow: hidden
. Then, all images can be easily aligned in a row with display: inline-block
and white-space: nowrap
set on the container (with flexbox
now it is even easier).
For n = 4
The DOM structure would be something like this:
offset(px) 0 100 200 300 400 500
images | 4c | 1 | 2 | 3 | 4 | 1c
/* ^^ ^^
[ Clone of the last image ] [ Clone of the 1st image ]
*/
At the beginning, your container will be positioned with left: -100px
(or also margin-left: -100px
or even better (for a matter of performance) transform: translateX(-100px)
), so the slider can show the first image. To switch from an image to another you will need to apply a javascript animation over the same property you've previously chosen.
When your slider is currently at the 4th image, you have to switch from image 4
to 1c
, so the idea is to execute a callback at the end of the animation that soon reposition your slider wrapper at the real 1st image offset (e.g. you set left: -100px
to the container)
This is analogous when your slider is currently positioned on the 1st element: to show the previous image you just need to perform an animation from image 1
to 4c
and when animation has been completed you just move the container so the slider is istantly positioned at the 4th image offset (e.g. you set left: -400px
to the container).
You can see the effect on the above fiddle: this is the minimal js/jquery
code I used (of course the code can be even optimized so the width of the items is not hardcoded into the script)
$(function() {
var gallery = $('#gallery ul'),
items = gallery.find('li'),
len = items.length,
current = 1, /* the item we're currently looking */
first = items.filter(':first'),
last = items.filter(':last'),
triggers = $('button');
/* 1. Cloning first and last item */
first.before(last.clone(true));
last.after(first.clone(true));
/* 2. Set button handlers */
triggers.on('click', function() {
var cycle, delta;
if (gallery.is(':not(:animated)')) {
cycle = false;
delta = (this.id === "prev")? -1 : 1;
/* in the example buttons have id "prev" or "next" */
gallery.animate({ left: "+=" + (-100 * delta) }, function() {
current += delta;
/**
* we're cycling the slider when the the value of "current"
* variable (after increment/decrement) is 0 or when it exceeds
* the initial gallery length
*/
cycle = (current === 0 || current > len);
if (cycle) {
/* we switched from image 1 to 4-cloned or
from image 4 to 1-cloned */
current = (current === 0)? len : 1;
gallery.css({left: -100 * current });
}
});
}
});
});
As mentioned before, this solution doesn't require really much effort and talking about performance, comparing this approach to a normal slider without looping, it only requires to make two additional DOM insertion when the slider is initialized and some (quite trivial) extra logic to manage a backward/forward loop.
Here is another example when you see two elements at once: in that case you need to clone more elements and make some simple changes to the logic
https://codepen.io/fcalderan/pen/bGbjZdz
I don't know if a simpler or better approach exists, but hope this helps anyway.
Note: if you need to also have a responsive gallery, maybe this answer may help too
Jquery simple infinite loop slider logic
$( document ).ready(function() { var src = 'img/img1.jpg'; function adjustSlideContainer ( adjustPositionAmount ) { //get all the images var $thumbnails = $( 'img.sl_thumb' ); //find the current one shown var $current = $thumbnails.filter( '[src="'+ src +'"]' ); //get its index var currentIndex = $thumbnails.index( $current ); //adjust the index to the image that should be shown next var nextIndex = currentIndex + adjustPositionAmount; if ( nextIndex < 0 ) { //if it's before the first one, wrap to the last one nextIndex = $thumbnails.length - 1; } else if ( nextIndex >= $thumbnails.length ) { //if it's after the end one, wrap to the first one nextIndex = 0; } src = $thumbnails.eq( nextIndex ).attr( 'src' ); $( '.slide_container' ).css( 'background-image', 'url(' + src + ')' ); } $(".right_nav_link").click(function() { //go to the next image adjustSlideContainer( 1 ); }); $(".left_nav_link").click(function() { //go to the previous image adjustSlideContainer( -1 ); });});
Making a loop slider
When you move to the next slide you check if you're at the last one and move the first slide back to the right. But because you always show part of the next image you're actually too late at that point: you need to check for the 'one before last' slide. This will give you time to move the next slide into place. Same in the other direction.
You're also doing the same check multiple times (you check if you need to call readyNextSlide
, but inside that function you're doing the same check again. So I moved readyNextSlide
into the goToSlide
function for simplicity.
The below code isn't perfect (for example, it doesn't show slide 6 at the start because it is moved to the back (you should be able to fix that by starting at slide #2) and clicking very quickly will make the slides disappear for a bit (fixable by adding some sort of blocking to clicking until the movement is done, or by using the slide positions instead of just the index in code) but it should help.
See the changes to readyNextSlide
which now moves the slides 220% instead of 110%, and is called when you're at the one before last or one before first slide:
// slider
const slides = document.getElementsByClassName("slide"); // this selection is a live collection; any changes in DOM is updated in the variable unlike querySelectors
const btnLeft = document.querySelector(`.btn-left`);
const btnRight = document.querySelector(`.btn-right`);
let currentSlideIndex = 0;
let lastSlideIndex = slides.length - 1;
// go to a slide;
function goToSlide(slideIndex) {
[...slides].forEach((s, i) => {
s.style.transform = `translateX(${110 * (i - slideIndex)}%)`
})
currentSlideIndex = slideIndex;
readyNextSlide();
}
goToSlide(currentSlideIndex);
// make ready the next slide if current slide is the first or the last slide
function readyNextSlide() {
// if currentSlide is the last slide, shift the first slide to the end
if (currentSlideIndex === lastSlideIndex - 1) {
slides[lastSlideIndex].insertAdjacentElement("afterend", slides[0]);
slides[lastSlideIndex].style.transform = `translateX(${220}%)`;
currentSlideIndex--; //this is because current slide is now the second last slide
}
// if currentSlide is the first slide, shift the last slide to the beginning
if (currentSlideIndex === 1) {
slides[0].insertAdjacentElement("beforebegin", slides[lastSlideIndex]);
slides[0].style.transform = `translateX(-${220}%)`;
currentSlideIndex++; //this is because current slide is now the second slide
}
}
// put the last slide in the beginning; ('if' condition is not necessary but providing if condition is future proof if user sets the initial slide to be shown as the last slide )
// shift all slides left or right based on direction provided
function shiftSlides(direction) {
direction ? currentSlideIndex++ : currentSlideIndex--
if (currentSlideIndex === lastSlideIndex || currentSlideIndex === 0) readyNextSlide();
goToSlide(currentSlideIndex);
}
//button click events
btnRight.addEventListener("click", shiftSlides.bind(null, 1));
btnLeft.addEventListener("click", shiftSlides.bind(null, 0));
slick slider - css transition infinite loop bug
Solution 1 - Use Flickity
If you want to try another carousel control, you can have a look at Flickity. According to my tests with the wrapAround option, it does not "snap" the first item back into position when the full cycle is complete; the transition goes on smoothly. You can see it at work in this jsfiddle.
As for the problem with formatting the items according to their even/odd index, it happens only when you have an odd number of items. One solution would be to duplicate the list of items. Intead of
Item 1 / Item 2 / Item 3 / Item 4 / Item 5
you could define
Item 1 / Item 2 / Item 3 / Item 4 / Item 5 / Item 1 / Item 2 / Item 3 / Item 4 / Item 5
That would ensure that you work with an even number of items.
Solution 2 - Slick Slider: add transition delay
With Slick Slider, adding a delay to the transition helps to make it smoother when the cycle completes. In the code snippet below, I replaced:
ul li .content {
transition: transform 0.5s linear;
...
}
ul li:not(.slick-current) .content {
transform: scale(0.9);
}
with
ul li .content {
transition: transform 0.3s linear;
transition-delay: 0.5s;
...
}
ul li:not(.slick-current) .content {
transform: scale(0.9);
transition-delay: 0s;
}
$(document).ready(function() { $('.items').slick({ infinite: true, speed: 500, slidesToShow: 2, variableWidth: false });});
* { margin: 0; padding: 0;}
ul { list-style: none; height: 150px;}
.slick-list,.slick-track { height: 100%;}
ul li { width: 350px; height: 100%;}
ul li .content { width: 100%; height: 100%; transition: transform 0.3s linear; transition-delay: 0.5s; text-align: center;}
ul li .content span { color: #fff; font-size: 50px; font-family: Arial; position: relative; top: 50%; transform: translateY(-50%); display: block;}
ul li:nth-child(odd) .content { background-color: red;}
ul li:nth-child(even) .content { background-color: green;}
ul li:not(.slick-current) .content { transform: scale(0.9); transition-delay: 0s;}
<link href="https://cdn.jsdelivr.net/jquery.slick/1.6.0/slick.css" rel="stylesheet" /><script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/jquery.slick/1.6.0/slick.min.js"></script><ul class="items"> <li class="item"> <div class="content"> <span>1</span> </div> </li> <li class="item"> <div class="content"> <span>2</span> </div> </li> <li class="item"> <div class="content"> <span>3</span> </div> </li> <li class="item"> <div class="content"> <span>4</span> </div> </li> <li class="item"> <div class="content"> <span>5</span> </div> </li> <li class="item"> <div class="content"> <span>1</span> </div> </li> <li class="item"> <div class="content"> <span>2</span> </div> </li> <li class="item"> <div class="content"> <span>3</span> </div> </li> <li class="item"> <div class="content"> <span>4</span> </div> </li> <li class="item"> <div class="content"> <span>5</span> </div> </li></ul>
how to make an infinite loop? (slider javascript)
You could take the rest of the value with the reminder operator %
and the given length of the slides.
this.slide[this.nbrClick % this.nbr].classList = "slide left";
this.slide[(this.nbrClick + 1) % this.nbr].classList = "slide center";
this.slide[(this.nbrClick + 2) % this.nbr].classList = "slide right";
The remainder operator returns the remainder left over when one operand is divided by a second operand. It always takes the sign of the dividend.
For example if you take 10 and want a remainder value with 3, the result is 1.
10 - Math.floor(10 / 3) * 3 = 1
or a bit more practical with the value of the code:
0 % 3 = 0
1 % 3 = 1
2 % 3 = 2
3 % 3 = 0
4 % 3 = 1
5 % 3 = 2
class Slider { constructor() { this.slide = document.getElementsByClassName("slide"); this.next = document.getElementById("next"); this.nbr = this.slide.length; this.nbrClick = 0;
}
click() { this.next.addEventListener("click", () => { this.nbrClick++; if (this.nbrClick > this.nbr) { this.nbrClick = 1; } for (let i = 0; i < this.nbr; i++) { this.slide[i].classList = "slide hidden"; } this.slide[this.nbrClick % this.nbr].classList = "slide left"; this.slide[(this.nbrClick + 1) % this.nbr].classList = "slide center"; this.slide[(this.nbrClick + 2) % this.nbr].classList = "slide right"; }); }}
let slider = new Slider();slider.click();
#slider { position: relative; height: 200px; display: flex; }.slide { height: 200px; width: 200px; position: absolute; border: 1px solid black; display: flex; justify-content: center; align-items: center; }.left { transform: translateX(0px); }.center { transform: translateX(200px); }.right { transform: translateX(400px); }.hidden { display: none; }
<div id="slider" style="display: flex"> <div class="slide left">1</div> <div class="slide center">2</div> <div class="slide right">3</div> <div class="slide hidden">4</div></div>
<button id="next">Next</button>
Related Topics
Jquery Mobile - $.Mobile.Changepage Not Loading External .Js Files
How to Read Xml File Contents in Jquery and Display in HTML Elements
Attach an Event in a Child Iframe to a Handler in the Parent Window
What HTML Tags Support the Onload/Onerror JavaScript Event Attributes
Getting Unparsed (Raw) HTML with JavaScript
How to Solve Uncaught Rangeerror When Download Large Size JSON
Jquery $("#Radiobutton").Change(...) Not Firing During De-Selection
How to Select Element That Does Not Have Specific Class
Chrome Autofill/Autocomplete No Value for Password
How to Add and Remove Classes in JavaScript Without Jquery
How to Detect Element Being Added/Removed from Dom Element
Phonegap Inappbrowser Display PDF 2.7.0
Trigger Standard HTML5 Validation (Form) Without Using Submit Button
Angularjs Does Not Load Scripts Within Ng-View