user8227676
user8227676

Reputation: 65

Trying to make continuous JavaScript slider

The problem with my slider is that when it gets to the last slide and i click next it jumps over the two slides to get to the first one. Similarly when i am on the first slide and click previous, it jumps over slides to get to the last one. I would like to make it that when i get to the last slide and click NEXT the first slide would come from the right to left. (similar concept for the PREVIOUS button on first slide). I tried using insertBefore() and appendChild() for the slides but couldn't figure it out...

Here is my code:

// Slider
const slider_wrapp = document.querySelector('.tract-slider');
const slider = document.querySelector('.tract-slider-wrapp');
var slide = document.getElementsByClassName('tract-slide');

const leftBtn = document.querySelector('.slide-left');
const rightBtn = document.querySelector('.slide-right');

let swWidth = slider_wrapp.clientWidth;
let sliderWidth = swWidth * slide.length;
let slideWidth = 0;

slider.style.width = sliderWidth + "px";

for (var i = 0; i < slide.length; i++) {
  slide.item(i).style.width = swWidth + "px";
}

function moveRight() {
  slideWidth === sliderWidth - swWidth ? slideWidth = 0 : slideWidth += swWidth;
  slider.style.transform = "translateX(" + (-slideWidth) + "px)";

}

function moveLeft() {
  slideWidth === 0 ? slideWidth = sliderWidth - swWidth : slideWidth -= swWidth;
  slider.style.transform = "translateX(" + (-slideWidth) + "px)";
}

rightBtn.addEventListener("click", function() {
  moveRight();
});

leftBtn.addEventListener("click", function() {
  moveLeft();
});
.tract-slider {
  width: 100%;
  height: 75vh;
  position: relative;
  overflow: hidden;
}

.tract-slider-wrapp {
  height: 100%;
  position: relative;
  -webkit-transition: all 350ms cubic-bezier(.08, .13, 0, .81);
  -o-transition: all 350ms cubic-bezier(.08, .13, 0, .81);
  transition: all 350ms cubic-bezier(.08, .13, 0, .81);
}

.tract-slide {
  height: 100%;
  float: left;
  position: relative;
  display: block;
  background-position: center;
  -webkit-background-size: cover;
  background-size: cover;
}

.tract-slide:nth-child(1) {
  background-image: url("https://static.pexels.com/photos/126282/pexels-photo-126282.jpeg");
}

.tract-slide:nth-child(2) {
  background-image: url("https://static.pexels.com/photos/29017/pexels-photo-29017.jpg");
}

.tract-slide:nth-child(3) {
  background-image: url("https://static.pexels.com/photos/70760/dandelion-dandelion-seeds-taraxacum-fluffy-70760.jpeg");
}

.tract-slider-control {
  position: absolute;
  left: 0;
  bottom: 0;
  background: #ffffff;
  padding: 1em;
}

.tract-slider-btn {
  display: inline-block;
  cursor: pointer;
  margin-left: 1em;
}

.tract-slider-btn:nth-child(1) {
  margin-left: 0;
}
<div class="tract-slider">
  <div class="tract-slider-wrapp">
    <div class="tract-slide"></div>
    <div class="tract-slide"></div>
    <div class="tract-slide"></div>
  </div>
  <div class="tract-slider-control">
    <div class="tract-slider-btn slide-left">Prev</div>
    <div class="tract-slider-btn slide-right">Next</div>
  </div>
</div>

PS. Please use JavaScript for solution

Upvotes: 3

Views: 1171

Answers (1)

tao
tao

Reputation: 90013

Creating an infinite slider means you need to move your slides around in DOM so they give the impression of a continuous track.

The first thing you need to change is having their backgrounds tied up to their position in DOM. If we want to slide back from first slide to the last one, we need to take the last slide, prepend it before the first one but, considering your current CSS, that will change the backgrounds of all slides, as they are currently bound to their position in DOM (...:nth-child {background-image:...}...).

The second thing that needs changing is positioning the slides into the slider track. If they're floated, whenever we change their order, all the rest of the slides will be affected. By positioning them with position:absolute each slide moves independently, without affecting the others, so it's easier to rearrange them while keeping control.

Long story short, I started from scratch and placed all methods inside a single object: theSlider.

The reset() function does the heavy lifting: it puts before class on first element, current on second and after on all the rest. So you have to put the "last" slide first, because the slider will start with it appended before the "current" one.

The sliding is done by applying go-left and go-right classes to the track. After the transition is done, I just move the first/last slide into the new position, depending on case, and run reset() again (which strips all classes and reapplies them based on new positions).
Animations are handled by CSS. All JavaScript does is apply/remove classes and move the slides in DOM.

var theSlider = {
  track : document.querySelector('.tract-slider-wrapp'),
  // has to match `transition-duration` in CSS:
  duration : 600,
  reset : function() {
    var slides = document.querySelectorAll('.tract-slider-wrapp > div');
    for (var i = 0; i < slides.length; i++) {
      slides[i].className = '';
      slides[i].classList.add(i > 1? 'after' : (i ? 'current':'before'))
    }
  },
  init : function() {
    theSlider.reset();
    theSlider.track.classList.remove('not-loaded')
  },
  next : function() {
    theSlider.track.classList.add('go-right');
    setTimeout(function(){
      var firstSlide = document.querySelector('.tract-slider-wrapp > div:first-child');
      theSlider.track.appendChild(firstSlide);
      theSlider.reset();
      theSlider.track.classList.remove('go-right')
    },theSlider.duration)
  },
  prev : function() {
    theSlider.track.classList.add('go-left');
    setTimeout(function() {
      var lastSlide = document.querySelector('.tract-slider-wrapp > div:last-child');
      theSlider.track.insertBefore(lastSlide, theSlider.track.firstChild);
      theSlider.reset();
      theSlider.track.classList.remove('go-left')
    },theSlider.duration)
  },
  prevButton : document.querySelector('.slide-left'),
  nextButton : document.querySelector('.slide-right')
};
window.addEventListener("load", theSlider.init);
theSlider.prevButton.addEventListener('click', theSlider.prev);
theSlider.nextButton.addEventListener('click', theSlider.next);
.tract-slider {
  width: 100%;
  height: 75vh;
  position: relative;
  overflow: hidden;
  background-color: #f5f5f5;
}

.tract-slider-wrapp {
  height: 100%;
  transition: all 350ms cubic-bezier(.08, .13, 0, .81);
  opacity: 1;
}
.tract-slider-wrapp.not-loaded {
  opacity: 0;
}

.tract-slider-wrapp>div {
  height: 100%;
  position: absolute;
  background: transparent no-repeat 50% 50% /cover;
  width: 100%;
}
.tract-slider-wrapp > div.before {
  margin-left: -100%;
}
.tract-slider-wrapp > div.current + div {
  margin-left: 100%;
}
.tract-slider-wrapp > div.after ~ div {
  opacity: 0;
}
.tract-slider-control {
    position: absolute;
    width: 100%;
    z-index: 1;
    top: 50%;
    display: flex;
    justify-content: space-between;
    transform: translateY(-50%);
}
.tract-slider-control div {
  background-color: rgba(0,0,0,.35);
  padding: .5rem 1rem;
  color: white;
  cursor: pointer;
}
.tract-slider-control :first-child {
  border-radius: 0 17px 17px 0;
}
.tract-slider-control :last-child { 
  border-radius: 17px 0 0 17px;
}
body {
  margin: 0;
}
.go-right div {
  transform: translateX(-100%);
}
.go-left div {
  transform: translateX(100%);
}
.go-right div, .go-left div {
  transition-property: transform;
  transition-timing-function: cubic-bezier(.4,0,.2,1);
  /* has to match `duration` in js: */
  transition-duration: 600ms;
}
<div class="tract-slider">
  <div class="tract-slider-wrapp not-loaded">
    <div style="background-image:url('https://static.pexels.com/photos/126282/pexels-photo-126282.jpeg')"></div>
    <div style="background-image:url('https://static.pexels.com/photos/29017/pexels-photo-29017.jpg')"></div>
    <div style="background-image:url('https://static.pexels.com/photos/70760/dandelion-dandelion-seeds-taraxacum-fluffy-70760.jpeg')"></div>
  </div>
  <div class="tract-slider-control">
    <div class="tract-slider-btn slide-left">Prev</div>
    <div class="tract-slider-btn slide-right">Next</div>
  </div>
</div>

If you want to change the animation duration you need to change it in both js and css.
The only current limitation is it needs at least 3 slides to work. I guess it could be adjusted to work with only two slides by: cloning the "inactive" slide into third position, removing the clone after transition and cloning the other one.


ToDo's:

  • prefix CSS so it works in more browsers
  • replace .classList.add('whatever') with .className += ' whatever' and
    .classList.remove('whatever') with .className.replace('whatever', '') if you want to show IE some love.

I told the above just to tell you this: if you want to get going, don't reinvent the wheel.

It's great you use vanilla javascript. But sooner or later you'll end up writing your own wrappers for common things. Depending on how good you are/have become, you'll write your own, limited, custom version of jQuery. Allow me to put things into perspective: Google included a lite version of jQuery into AngularJS. It's that good.

You, as an single developer, do not stand a chance at writing a better, more streamlined and tested version of it. And besides, you don't have to. Use your skill and abilities to go forward, not sideways.

Upvotes: 3

Related Questions