Karl Gin Frank
Karl Gin Frank

Reputation: 23

How to loop slides and autoplay in custom javascript?

I'm a beginner in javascript. I downloaded a custom javacsript slide from this website:

https://www.cssscript.com/swiper-thumbnail-paginator/

Slide demo here: https://www.cssscript.com/demo/swiper-thumbnail-paginator/

I've also created a demo on jsfiddle: https://jsfiddle.net/t4c1nb3g/

The file consists of 5 files, namely: index.html, style.css, debounce.js, script.js, slide.js

And it has 6 images.

Currently, there is no slide loop in the demo, I want the slide to start itself (autoplay) and repeat it again and again (looping).

How can I make it loop for the slide and start itself (autoplay)?

Below is the index.html file:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Slider</title>
  <link rel="stylesheet" href="css/style.css">
</head>

<body>
  <div class="slide-wrapper">
    <ul class="slide">
      <li><img src="https://i.sstatic.net/2fkLR.jpg" alt=""></li>
      <li><img src="https://i.sstatic.net/gN1Ri.jpg" alt=""></li>
      <li><img src="https://i.sstatic.net/FgqYP.jpg" alt=""></li>
      <li><img src="https://i.sstatic.net/su1na.jpg" alt=""></li>
      <li><img src="https://i.sstatic.net/vZYry.jpg" alt=""></li>
      <li><img src="https://i.sstatic.net/5dXtQ.jpg" alt=""></li>
    </ul>
  </div>
  <div class="wrap-controls">
    <div class="arrow-nav">
      <button class="prev"></button>
    </div>
    <ul class="custom-controls">
      <li><img src="https://i.sstatic.net/2fkLR.jpg" alt=""></li>
      <li><img src="https://i.sstatic.net/gN1Ri.jpg" alt=""></li>
      <li><img src="https://i.sstatic.net/FgqYP.jpg" alt=""></li>
      <li><img src="https://i.sstatic.net/su1na.jpg" alt=""></li>
      <li><img src="https://i.sstatic.net/vZYry.jpg" alt=""></li>
      <li><img src="https://i.sstatic.net/5dXtQ.jpg" alt=""></li>
    </ul>
    <div class="arrow-nav">
      <button class="next"></button>
    </div>
  </div>
  <script type="module" src="js/script.js"></script>
</body>

</html>

Below is the style.css file:

body {
  margin: 0px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
}

ul {
  padding: 0px;
  margin: 0px;
  list-style: none;
}

img {
  display: block;
  max-width: 100%;
}

.slide-wrapper {
  overflow: hidden;
}

.slide {
  display: flex;
}

.slide:hover {
  will-change: transform;
}

.slide li {
  flex-shrink: 0;
  max-width: 600px;
  margin: 0 20px;
  border-radius: 4px;
  overflow: hidden;
  box-shadow: 0 2px 4px rgba(0,0,0,.4);
  opacity: .8;
  transform: scale(.8);
  transition: .4s;
}

.slide li.active {
  opacity: 1;
  transform: scale(1);
}

[data-control="slide"] {
  display: flex;
  justify-content: center;
  margin-top: 20px;
}

[data-control="slide"] li a {
  display: block;
  width: 12px;
  height: 12px;
  background: #FB5;
  border-radius: 50%;
  overflow: hidden;
  text-indent: -999px;
  margin: 5px;
}

[data-control="slide"] li.active a, [data-control="slide"] li a:hover {
  background: #E54;
}

.custom-controls {
  display: flex;
  justify-content: center;
  margin-top: 40px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.custom-controls li {
  opacity: .8;
  transform: scale(.8);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  overflow: hidden;
  margin: 2px;
  box-shadow: 0 2px 2px rgba(0,0,0,.5);
  transition: .3s;
  cursor: pointer;
}

.custom-controls li.active {
  opacity: 1;
  transform: scale(1);
}

.arrow-nav {
  display: flex;
  justify-content: space-around;
  margin: 20px 10px 0 10px;
}

.arrow-nav button {
  cursor: pointer;
  border: none;
  border-radius: 50%;
  color: white;
  width: 30px;
  height: 30px;
  background: #999 url('../img/arrow.svg') center center no-repeat;
  outline: none;
}

.arrow-nav button:hover {
  background: #333 url('../img/arrow.svg') center center no-repeat;
  transition: ease-in-out .3s;
}

.arrow-nav button.prev {
  transform: rotate(-180deg);
}

.wrap-controls {
  display: flex;
  justify-content: center;
  align-items: center;
}

Below is the debounce.js file:

export default function debounce(callback, delay) {
  let timer;
  return (...args) => {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      callback(...args);
      timer = null;
    }, delay);
  };
}

Below is the script.js file:

import SlideNav from './slide.js';

const slide = new SlideNav('.slide', '.slide-wrapper');
slide.init();
slide.addArrow('.prev', '.next');
slide.addControl('.custom-controls');

Below is the slide.js file:

import debounce from './debounce.js';

export class Slide {
  constructor(slide, wrapper) {
    this.slide = document.querySelector(slide)
    this.wrapper = document.querySelector(wrapper);
    this.dist = { finalPosition: 0, startX: 0, movement: 0 }
    this.activeClass = 'active';
    this.changeEvent = new Event('changeEvent');
  }

  transition(active) {
    this.slide.style.transition = active ? 'transform .3s' : '';
  }

  moveSlide(distX) {
    this.dist.movePosition = distX;
    this.slide.style.transform = `translate3d(${distX}px, 0, 0)`;
  }

  updatePosition(clientX) {
    this.dist.movement = (this.dist.startX - clientX) * 1.6;
    return this.dist.finalPosition - this.dist.movement;
  }

  onStart(event) {
    let movetype;
    if (event.type === 'mousedown') {
      event.preventDefault();
      this.dist.startX = event.clientX;
      movetype = 'mousemove';
    } else {
      this.dist.startX = event.changedTouches[0].clientX;
      movetype = 'touchmove';
    }
    this.wrapper.addEventListener(movetype, this.onMove);
    this.transition(false);
  }

  onMove(event) {
    const pointerPosition = (event.type === 'mousemove') ? event.clientX : event.changedTouches[0].clientX;
    const finalPosition = this.updatePosition(pointerPosition);
    this.moveSlide(finalPosition);
  }

  onEnd(event) {
    const movetype = (event.type === 'mouseup') ? 'mousemove' : 'touchmove';
    this.wrapper.removeEventListener(movetype, this.onMove);
    this.dist.finalPosition = this.dist.movePosition;
    this.transition(true);
    this.changeSlideOnEnd();
  }

  changeSlideOnEnd() {
    if (this.dist.movement > 120 && this.index.next !== undefined) {
      this.activeNextSlide();
    } else if (this.dist.movement < -120 && this.index.prev !== undefined) {
      this.activePrevSlide();
    } else {
      this.changeSlide(this.index.active);
    }
  }

  addSlideEvents() {
    this.wrapper.addEventListener('mousedown', this.onStart);
    this.wrapper.addEventListener('touchstart', this.onStart);
    this.wrapper.addEventListener('mouseup', this.onEnd);
    this.wrapper.addEventListener('touchend', this.onEnd);
  }

  // Slides config

  slidePosition(slide) {
    const margin = (this.wrapper.offsetWidth - slide.offsetWidth) / 2;
    return -(slide.offsetLeft - margin);
  }

  slidesConfig() {
    this.slideArray = [...this.slide.children].map((element) => {
      const position = this.slidePosition(element);
      return { position, element };
    });
  }

  slidesIndexNav(index) {
    const last = this.slideArray.length - 1;
    this.index = {
      prev: index ? index - 1 : undefined,
      active: index,
      next: index === last ? undefined : index + 1,
    }
  }

  changeSlide(index) {
    const activeSlide = this.slideArray[index];
    this.moveSlide(activeSlide.position);
    this.slidesIndexNav(index);
    this.dist.finalPosition = activeSlide.position;
    this.changeActiveClass();
    this.wrapper.dispatchEvent(this.changeEvent);
  }

  changeActiveClass() {
    this.slideArray.forEach(item => item.element.classList.remove(this.activeClass));
    this.slideArray[this.index.active].element.classList.add(this.activeClass);
  }

  activePrevSlide() {
    if (this.index.prev !== undefined) this.changeSlide(this.index.prev);
  }

  activeNextSlide() {
    if (this.index.next !== undefined) this.changeSlide(this.index.next);
  }

  onResize() {
    setTimeout(() => {
      this.slidesConfig();
      this.changeSlide(this.index.active);
    }, 1000);
  }

  addResizeEvent() {
    window.addEventListener('resize', this.onResize);
  }

  bindEvents() {
    this.onStart = this.onStart.bind(this);
    this.onMove = this.onMove.bind(this);
    this.onEnd = this.onEnd.bind(this);

    this.activePrevSlide = this.activePrevSlide.bind(this);
    this.activeNextSlide = this.activeNextSlide.bind(this);

    this.onResize = debounce(this.onResize.bind(this), 200);
  }

  init() {
    this.bindEvents();
    this.transition(true);
    this.addSlideEvents();
    this.slidesConfig();
    this.addResizeEvent();
    this.changeSlide(0);
    return this;
  }
}

export default class SlideNav extends Slide {
  constructor(slide, wrapper) {
    super(slide, wrapper);
    this.bindControlEvents();
  }

  addArrow(prev, next) {
    this.prevElement = document.querySelector(prev);
    this.nextElement = document.querySelector(next);
    this.addArrowEvent();
  }

  addArrowEvent() {
    this.prevElement.addEventListener('click', this.activePrevSlide);
    this.nextElement.addEventListener('click', this.activeNextSlide);
  }

  createControl() {
    const control = document.createElement('ul');
    control.dataset.control = 'slide';
    this.slideArray.forEach((item, index) => {
      control.innerHTML += `<li><a href="#slide${index + 1}">${index + 1}</a></li>`;
    });
    this.wrapper.appendChild(control);
    return control;
  }

  eventControl(item, index) {
    item.addEventListener('click', (event) => {
      event.preventDefault();
      this.changeSlide(index);
    });
    this.wrapper.addEventListener('changeEvent', this.activeControlItem);
  }

  activeControlItem() {
    this.controlArray.forEach(item => item.classList.remove(this.activeClass));
    this.controlArray[this.index.active].classList.add(this.activeClass);
  }

  addControl(customControl) {
    this.control = document.querySelector(customControl) || this.createControl();
    this.controlArray = [...this.control.children];

    this.activeControlItem();
    this.controlArray.forEach(this.eventControl);
  }

  bindControlEvents() {
    this.eventControl = this.eventControl.bind(this);
    this.activeControlItem = this.activeControlItem.bind(this);
  }
}

Thank you very much,

best regards.

Franks.

Upvotes: 2

Views: 418

Answers (1)

Yves
Yves

Reputation: 278

After debugging your case on Codepen, I finally figured it out.

Basically you need to know 2 changes that I made:

  1. The first one is handling the edge cases:

    • When you're at the end and click Next, you should go to the beginning

    • When you're at the beginning and click Previous, you should go to the end

  2. The 2nd aspect is making this slider autoplaying or looping.


Now for the code

For the 1st change (the edge cases), modify in your code the methods activePrevSlide and activeNextSlide as the following:

activePrevSlide() {
  if (this.index.prev !== undefined)
    this.changeSlide(this.index.prev);
  else
    this.changeSlide(this.slideArray.length - 1);
}

activeNextSlide() {
  if (this.index.next !== undefined)
    this.changeSlide(this.index.next);
  else
    this.changeSlide(0);
}

For the 2nd aspect, add at the end of script.js, this statement:

setInterval(slide.activeNextSlide, 3000);

It will autoplay each 3 seconds (you can change it definitely).



Finally, this is the full code, if you want to play here:

function debounce(callback, delay) {
  let timer;
  return (...args) => {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      callback(...args);
      timer = null;
    }, delay);
  };
}
class Slide {
  constructor(slide, wrapper) {
    this.slide = document.querySelector(slide)
    this.wrapper = document.querySelector(wrapper);
    this.dist = { finalPosition: 0, startX: 0, movement: 0 }
    this.activeClass = 'active';
    this.changeEvent = new Event('changeEvent');
  }

  transition(active) {
    this.slide.style.transition = active ? 'transform .3s' : '';
  }

  moveSlide(distX) {
    this.dist.movePosition = distX;
    this.slide.style.transform = `translate3d(${distX}px, 0, 0)`;
  }

  updatePosition(clientX) {
    this.dist.movement = (this.dist.startX - clientX) * 1.6;
    return this.dist.finalPosition - this.dist.movement;
  }

  onStart(event) {
    let movetype;
    if (event.type === 'mousedown') {
      event.preventDefault();
      this.dist.startX = event.clientX;
      movetype = 'mousemove';
    } else {
      this.dist.startX = event.changedTouches[0].clientX;
      movetype = 'touchmove';
    }
    this.wrapper.addEventListener(movetype, this.onMove);
    this.transition(false);
  }

  onMove(event) {
    const pointerPosition = (event.type === 'mousemove') ? event.clientX : event.changedTouches[0].clientX;
    const finalPosition = this.updatePosition(pointerPosition);
    this.moveSlide(finalPosition);
  }

  onEnd(event) {
    const movetype = (event.type === 'mouseup') ? 'mousemove' : 'touchmove';
    this.wrapper.removeEventListener(movetype, this.onMove);
    this.dist.finalPosition = this.dist.movePosition;
    this.transition(true);
    this.changeSlideOnEnd();
  }

  changeSlideOnEnd() {
    if (this.dist.movement > 120 && this.index.next !== undefined) {
      this.activeNextSlide();
    } else if (this.dist.movement < -120 && this.index.prev !== undefined) {
      this.activePrevSlide();
    } else {
      this.changeSlide(this.index.active);
    }
  }

  addSlideEvents() {
    this.wrapper.addEventListener('mousedown', this.onStart);
    this.wrapper.addEventListener('touchstart', this.onStart);
    this.wrapper.addEventListener('mouseup', this.onEnd);
    this.wrapper.addEventListener('touchend', this.onEnd);
  }

  // Slides config

  slidePosition(slide) {
    const margin = (this.wrapper.offsetWidth - slide.offsetWidth) / 2;
    return -(slide.offsetLeft - margin);
  }

  slidesConfig() {
    this.slideArray = [...this.slide.children].map((element) => {
      const position = this.slidePosition(element);
      return { position, element };
    });
  }

  slidesIndexNav(index) {
    const last = this.slideArray.length - 1;
    this.index = {
      prev: index ? index - 1 : undefined,
      active: index,
      next: index === last ? undefined : index + 1,
    }
  }

  changeSlide(index) {
    const activeSlide = this.slideArray[index];
    this.moveSlide(activeSlide.position);
    this.slidesIndexNav(index);
    this.dist.finalPosition = activeSlide.position;
    this.changeActiveClass();
    this.wrapper.dispatchEvent(this.changeEvent);
  }

  changeActiveClass() {
    this.slideArray.forEach(item => item.element.classList.remove(this.activeClass));
    this.slideArray[this.index.active].element.classList.add(this.activeClass);
  }

  activePrevSlide() {
    if (this.index.prev !== undefined)
      this.changeSlide(this.index.prev);
    else
      this.changeSlide(this.slideArray.length - 1);
  }

  activeNextSlide() {
    if (this.index.next !== undefined)
      this.changeSlide(this.index.next);
    else
      this.changeSlide(0);
  }

  onResize() {
    setTimeout(() => {
      this.slidesConfig();
      this.changeSlide(this.index.active);
    }, 1000);
  }

  addResizeEvent() {
    window.addEventListener('resize', this.onResize);
  }

  bindEvents() {
    this.onStart = this.onStart.bind(this);
    this.onMove = this.onMove.bind(this);
    this.onEnd = this.onEnd.bind(this);

    this.activePrevSlide = this.activePrevSlide.bind(this);
    this.activeNextSlide = this.activeNextSlide.bind(this);

    this.onResize = debounce(this.onResize.bind(this), 200);
  }

  init() {
    this.bindEvents();
    this.transition(true);
    this.addSlideEvents();
    this.slidesConfig();
    this.addResizeEvent();
    this.changeSlide(0);
    return this;
  }
}

class SlideNav extends Slide {
  constructor(slide, wrapper) {
    super(slide, wrapper);
    this.bindControlEvents();
  }

  addArrow(prev, next) {
    this.prevElement = document.querySelector(prev);
    this.nextElement = document.querySelector(next);
    this.addArrowEvent();
  }

  addArrowEvent() {
    this.prevElement.addEventListener('click', this.activePrevSlide);
    this.nextElement.addEventListener('click', this.activeNextSlide);
  }

  createControl() {
    const control = document.createElement('ul');
    control.dataset.control = 'slide';
    this.slideArray.forEach((item, index) => {
      control.innerHTML += `<li><a href="#slide${index + 1}">${index + 1}</a></li>`;
    });
    this.wrapper.appendChild(control);
    return control;
  }

  eventControl(item, index) {
    item.addEventListener('click', (event) => {
      event.preventDefault();
      this.changeSlide(index);
    });
    this.wrapper.addEventListener('changeEvent', this.activeControlItem);
  }

  activeControlItem() {
    this.controlArray.forEach(item => item.classList.remove(this.activeClass));
    this.controlArray[this.index.active].classList.add(this.activeClass);
  }

  addControl(customControl) {
    this.control = document.querySelector(customControl) || this.createControl();
    this.controlArray = [...this.control.children];

    this.activeControlItem();
    this.controlArray.forEach(this.eventControl);
  }

  bindControlEvents() {
    this.eventControl = this.eventControl.bind(this);
    this.activeControlItem = this.activeControlItem.bind(this);
  }
}


const slide = new SlideNav('.slide', '.slide-wrapper');
slide.init();
slide.addArrow('.prev', '.next');
slide.addControl('.custom-controls');

setInterval(slide.activeNextSlide, 3000);
body {
  margin: 0px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
}

ul {
  padding: 0px;
  margin: 0px;
  list-style: none;
}

img {
  display: block;
  max-width: 100%;
}

.slide-wrapper {
  overflow: hidden;
}

.slide {
  display: flex;
}

.slide:hover {
  will-change: transform;
}

.slide li {
  flex-shrink: 0;
  max-width: 600px;
  margin: 0 20px;
  border-radius: 4px;
  overflow: hidden;
  box-shadow: 0 2px 4px rgba(0,0,0,.4);
  opacity: .8;
  transform: scale(.8);
  transition: .4s;
}

.slide li.active {
  opacity: 1;
  transform: scale(1);
}

[data-control="slide"] {
  display: flex;
  justify-content: center;
  margin-top: 20px;
}

[data-control="slide"] li a {
  display: block;
  width: 12px;
  height: 12px;
  background: #FB5;
  border-radius: 50%;
  overflow: hidden;
  text-indent: -999px;
  margin: 5px;
}

[data-control="slide"] li.active a, [data-control="slide"] li a:hover {
  background: #E54;
}

.custom-controls {
  display: flex;
  justify-content: center;
  margin-top: 40px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.custom-controls li {
  opacity: .8;
  transform: scale(.8);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  overflow: hidden;
  margin: 2px;
  box-shadow: 0 2px 2px rgba(0,0,0,.5);
  transition: .3s;
  cursor: pointer;
}

.custom-controls li.active {
  opacity: 1;
  transform: scale(1);
}

.arrow-nav {
  display: flex;
  justify-content: space-around;
  margin: 20px 10px 0 10px;
}

.arrow-nav button {
  cursor: pointer;
  border: none;
  border-radius: 50%;
  color: white;
  width: 30px;
  height: 30px;
  background: #999 url('../img/arrow.svg') center center no-repeat;
  outline: none;
}

.arrow-nav button:hover {
  background: #333 url('../img/arrow.svg') center center no-repeat;
  transition: ease-in-out .3s;
}

.arrow-nav button.prev {
  transform: rotate(-180deg);
}

.wrap-controls {
  display: flex;
  justify-content: center;
  align-items: center;
}
<div class="slide-wrapper">
  <ul class="slide">
    <li><img src="https://i.sstatic.net/2fkLR.jpg" alt=""></li>
    <li><img src="https://i.sstatic.net/gN1Ri.jpg" alt=""></li>
    <li><img src="https://i.sstatic.net/FgqYP.jpg" alt=""></li>
    <li><img src="https://i.sstatic.net/su1na.jpg" alt=""></li>
    <li><img src="https://i.sstatic.net/vZYry.jpg" alt=""></li>
    <li><img src="https://i.sstatic.net/5dXtQ.jpg" alt=""></li>
  </ul>
</div>
<div class="wrap-controls">
  <div class="arrow-nav">
    <button class="prev"></button>
  </div>
  <ul class="custom-controls">
    <li><img src="https://i.sstatic.net/2fkLR.jpg" alt=""></li>
    <li><img src="https://i.sstatic.net/gN1Ri.jpg" alt=""></li>
    <li><img src="https://i.sstatic.net/FgqYP.jpg" alt=""></li>
    <li><img src="https://i.sstatic.net/su1na.jpg" alt=""></li>
    <li><img src="https://i.sstatic.net/vZYry.jpg" alt=""></li>
    <li><img src="https://i.sstatic.net/5dXtQ.jpg" alt=""></li>
  </ul>
  <div class="arrow-nav">
    <button class="next"></button>
  </div>
</div>

Regards

Upvotes: 1

Related Questions