2

Update: 07/13

I've found a better method for working out the infinite scroll using a JS property called cloneNode and used that to prepend and append the clone slides to create the infinite effect.

I've also learned a more efficient way of writing a lot of this code and was able to prevent the event bubbling that was occurring as a result of the transitionend, so that's fixed.

So, two issues remain:

I'm still having trouble mapping the dots to the slides. The clones seem to be throwing off the slide array and trying to set the dots using data- wasn't working for me.

I'm using a transform to move the slides container to create the slideshow, but if I scale the window the slide container overflow becomes visible. Looking at www.ramtrucks.com, I noticed that while re-sizing the window the transform and width properties on the slideshow are changing dynamically. I looked into resize events for JS but none of them seemed to cooperate with my code and I think that's because I'm using a flexbox. So somehow I need to resize and change flex properties at the same time (I think?).

enter image description here

Here's where I'm at:

// ----- slideshow declarations ----- //
const slideShowContainer = document.querySelector('.slideShow');
const slidesContainer = document.querySelector('.slidesContainer');
const rightBtn = document.querySelector('#slideRight');
const leftBtn = document.querySelector('#slideLeft');
const slideShowInterval = 10000;

let slides = document.querySelectorAll('.slideCard');
let index = 1;
let currentSlide;
let dots;

const firstClone = slides[0].cloneNode(true);
const lastClone = slides[slides.length - 1].cloneNode(true);

firstClone.id = 'firstClone'
lastClone.id = 'lastClone'

slidesContainer.append(firstClone);
slidesContainer.prepend(lastClone);

const slideWidth = slides[index].clientWidth;

slidesContainer.style.transform = `translateX(${-slideWidth * index}px)`;
// -------------------- //


// ----- clone swap ----- // 
const slideCollection = () => document.querySelectorAll('.slideCard');

slidesContainer.addEventListener('transitionend', () => {
  slides = slideCollection();
  if (slides[index].id === firstClone.id) {
    index = 1;
    slidesContainer.style.transition = 'none';
    slidesContainer.style.transform = 'translateX(' + (-slideWidth * index) + 'px)';
  }
  slides = slideCollection();
  if (slides[index].id === lastClone.id) {
    index = slides.length - 2;
    slidesContainer.style.transition = 'none';
    slidesContainer.style.transform = 'translateX(' + (-slideWidth * index) + 'px)';
  }
});
// -------------------- //


// ----- nav buttons ----- //
const moveRight = () => {
  slides = slideCollection();
  if (index >= slides.length - 1) return;
  index++;
  slidesContainer.style.transition = 'transform 0.4s ease-in-out';
  slidesContainer.style.transform = 'translateX(' + (-slideWidth * index) + 'px)';
  closeDisclosure();
}

const moveLeft = () => {
  slides = slideCollection();
  if (index <= 0) return;
  index--;
  slidesContainer.style.transition = 'transform 0.4s ease-in-out';
  slidesContainer.style.transform = 'translateX(' + (-slideWidth * index) + 'px)';
  closeDisclosure();
}

rightBtn.addEventListener('click', moveRight);
leftBtn.addEventListener('click', moveLeft);
// -------------------- //


// ----- selection dots ----- //
const selectDotsGroup = () => document.querySelector('slideNumberDots');
const slideSelect = () => document.querySelectorAll('.slideDot');

const setCurrentSlide = () => {
  slideDots = slideSelect();
  slideDots[index - 1].classList.add('selectedSlide');
};

setCurrentSlide();
// -------------------- //


// ----- slide autoplay ----- //
const autoplay = () => {
  currentSlide = setInterval(() => {
    moveRight();
    closeDisclosure();
  }, slideShowInterval);
}

slidesContainer.addEventListener('mouseenter', () => {
  clearInterval(currentSlide);
})

slidesContainer.addEventListener('mouseleave', autoplay);

autoplay();
// -------------------- //


// ----- disclosure window scripts ----- // 
// open disclosure
let discBtn = document.getElementsByClassName("disclosurePrompt");
let disc;
for (disc = 0; disc < discBtn.length - 0; disc++) {
  discBtn[disc].addEventListener("click", function() {
    this.nextElementSibling.classList.add("discVisible");
  });
}

// close disclosure
let closeBtn = document.getElementsByClassName("fa-times");
let close;
for (close = 0; close < closeBtn.length - 0; close++) {
  closeBtn[close].addEventListener("click", function() {
    var slideDiscWindow = document.querySelectorAll(".discVisible");
    [].forEach.call(slideDiscWindow, function(el) {
      el.classList.remove("discVisible");
    });
  });
}

// close disclosure on slide change
function closeDisclosure() {
  var slideDiscWindow = document.querySelectorAll(".discVisible");
  [].forEach.call(slideDiscWindow, function(el) {
    el.classList.remove("discVisible");
  });
}
// -------------------- //
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  background-color: darkgrey;
}

html {
  font-size: 16px;
}

.slideShowWrapper {
  display: flex;
  flex-direction: row;
  position: relative;
  width: 100%;
  height: 40vw;
  margin: 0;
  padding: 0;
  overflow: hidden;
}


/* begin slideshow layout */

.slideShow {
  display: flex;
  justify-content: flex-start;
  flex-direction: row;
  position: relative;
  width: 100vw;
  height: 40vw;
  margin: 0 auto;
  padding: 0;
  overflow: hidden;
}

.slidesContainer {
  display: flex;
  flex: 1 0 100%;
  flex-direction: row;
  width: 100vw;
  height: 40vw;
  margin: 0;
  padding: 0;
}

.slideCard {
  display: flex;
  flex-direction: row;
  flex: 1 0 100%;
  position: relative;
  height: 40vw;
  width: 100vw;
  min-width: 100%;
  margin: 0;
  padding: 0;
  background-color: transparent;
}

.fa-chevron-right {
  display: block;
  opacity: 0;
  font-size: 2.3vw;
  position: absolute;
  top: 50%;
  right: 0;
  color: white;
  margin: 0 5%;
  padding: 0;
  width: auto;
  height: auto;
  z-index: 1;
  background-color: transparent;
  cursor: pointer;
  transform-origin: center;
  transition: transform 0.15s linear, opacity 0.15s linear;
}

.fa-chevron-left {
  display: block;
  opacity: 0;
  font-size: 2.3vw;
  position: absolute;
  top: 50%;
  left: 0;
  color: white;
  margin: 0 5%;
  padding: 0;
  width: auto;
  height: auto;
  z-index: 1;
  background-color: transparent;
  cursor: pointer;
  transition: transform 0.15s linear, opacity 0.15s linear;
}

.fa-chevron-right:hover {
  transform: scale(1.2);
}

.fa-chevron-left:hover {
  transform: scale(1.2);
}

.slideShowWrapper:hover .fa-chevron-right {
  opacity: 1;
}

.slideShowWrapper:hover .fa-chevron-left {
  opacity: 1;
}

.slideNumberDots {
  display: flex;
  flex-direction: row;
  justify-content: center;
  bottom: 0%;
  gap: 0.8vw;
  position: absolute;
  width: 100%;
  z-index: 1;
  margin: 0 auto;
  padding: 1vw;
  background-color: transparent;
  pointer-events: none;
}

.slideDot {
  display: flex;
  height: 0.8vw;
  width: 0.8vw;
  border-radius: 50%;
  border: 0px solid rgb(27, 27, 27);
  margin: 0;
  padding: 0;
  background-color: white;
  transform-origin: center;
  transition: transform 0.2s linear, background-color 0.2s linear;
  pointer-events: all;
}

.slideDot:hover {
  background-color: #1c69d3;
  transform-origin: center;
  transform: scale(1.3);
  cursor: pointer;
}

.slideDot.selectedSlide {
  background-color: #1c69d3;
  transform: scale(1.2);
  transform-origin: center;
  transition: color, transform 0.3s linear;
  outline: 0.15vw solid black;
  border-radius: 50%;
}

.disclosurePrompt {
  display: flex;
  font-family: BMWTypeNext Latin TT, 'DDC Heading Font Face', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  position: absolute;
  color: white;
  font-size: 0.8vw;
  font-weight: 400;
  line-height: 1.25;
  width: fit-content;
  height: fit-content;
  top: 95%;
  left: 5%;
  cursor: pointer;
  z-index: 2;
  user-select: none;
  outline: 1px transparent;
  text-decoration: underline;
}

.disclosurePrompt:hover {
  color: #e4e4e4;
}

.disclosurePrompt:focus {
  color: #e4e4e4;
}

.disclosureContainer {
  visibility: hidden;
  width: 90vw;
  height: auto;
  outline: 1px solid black;
  background-color: rgba(0, 0, 0, 0.95);
  position: absolute;
  margin: 0 auto;
  bottom: 5%;
  left: 5%;
  opacity: 0;
  z-index: 10;
  transition: opacity, top, 0.3s linear;
}

.disclosureContainer.discVisible {
  visibility: visible;
  bottom: 10.5%;
  opacity: 1;
}

.disclosureText {
  font-family: BMWTypeNext Latin TT, 'DDC Heading Font Face', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  color: white;
  line-height: clamp(0.7rem, -0.6rem + 3vw, 0.9rem);
  font-size: clamp(0.5rem, -0.875rem + 3vw, 0.7rem);
  display: block;
  margin: 0 auto;
  padding: 1.5rem 0.5rem 0.5rem 0.5rem;
  text-align: justify;
}

.fa-times {
  display: block;
  color: white;
  font-size: 0.8em;
  position: absolute;
  left: 0;
  top: 0;
  z-index: 12;
  padding: 0.5rem 0.5rem;
  transition: all 0.2s linear;
  cursor: pointer;
}

.fa-times:hover {
  color: #1c69d3;
  transition: all 0.2s linear;
}


/* end slideshow layout */


/* begin images */

.bmw2series {
  content: url("https://i.imgur.com/MABHqGy.jpg");
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}

.bmw3series {
  content: url("https://i.imgur.com/Ggy6iNU.jpg");
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}

.bmwX3 {
  content: url("https://i.imgur.com/ucYCFcu.jpg");
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}

.bmwiX {
  content: url("https://i.imgur.com/bQhvuOY.jpg");
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}

.bmw5series {
  content: url("https://i.imgur.com/sLYH9Gy.jpg");
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}

.bmwPreOwned {
  content: url("https://i.imgur.com/kuOWIEJ.jpg");
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}
<head>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous" referrerpolicy="no-referrer"
  />
</head>

<body>
  <!-- slideshow wrapper -->
  <div class="slideShowWrapper">
    <!-- slideshow controls -->
    <section id="controls">
      <a><i id="slideRight" class="fa fa-chevron-right" aria-label="Next Slide" data-slider-btn="next"></i></a>
      <a><i id="slideLeft" class="fa fa-chevron-left" aria-label="Previous Slide" data-slider-btn="prev"></i></a>
      <div class="slideNumberDots">
        <a class="slideDot" data-slide="0"></a>
        <a class="slideDot" data-slide="1"></a>
        <a class="slideDot" data-slide="2"></a>
        <a class="slideDot" data-slide="3"></a>
        <a class="slideDot" data-slide="4"></a>
        <a class="slideDot" data-slide="5"></a>
      </div>
    </section>
    <!-- slides container -->
    <div class="slidesContainer">
      <section class="slideCard" id="slide0" data-slide="0">
        <img class="bmw2series" alt="BMW 2 Series" />
        <a class="disclosurePrompt" alt="Disclosure">Important Information</a>
        <div class="disclosureContainer">
          <i class="fa fa-times"></i>
          <p class="disclosureText">
            Through June 30, 2022, lease offer available on new 2022 BMW 228i xDrive Gran Coupe models from participating BMW Centers through BMW Financial Services NA, LLC, to customers who meet BMW Financial Services' credit requirements. Offer not valid in Puerto
            Rico. Monthly lease payments of $459 per month for 36 months is based on an adjusted capitalized cost of $36,155 (MSRP of $40,895, including destination and handling fee of $995, less $3,915 capitalized cost reduction, $0 security deposit,
            and suggested dealer contribution of $825). Actual MSRP and dealer contribution may vary and could affect your monthly lease payment. Cash due at signing includes $3,915 capitalized cost reduction, $459 first month's payment, $925 acquisition
            fee and $0 security deposit. Lessee responsible for insurance during the lease term, excess wear and tear as defined in the lease contract, $0.25/mile over 30,000 miles, plus disposition fee of up to $495 (not to exceed an amount permissible
            by law) at lease end. Not all customers will qualify for security deposit waiver. Tax, title, license, registration and dealer fees are additional fees due at signing. Advertised payment does not include applicable taxes. Purchase option at
            lease end, excluding tax, title and government fees, is $23,719. Offer valid through June 30, 2022 and may be combined with other offers unless otherwise stated. Models pictured may be shown with metallic paint and/or additional accessories.
            Visit your authorized BMW Center for important details.
          </p>
        </div>
      </section>
      <section class="slideCard" id="slide1" data-slide="1">
        <img class="bmw3series" alt="BMW 3 Series" />
        <a class="disclosurePrompt" alt="Disclosure">Important Information</a>
        <div class="disclosureContainer">
          <i class="fa fa-times"></i>
          <p class="disclosureText">
            Through June 30, 2022, lease offer available on new 2022 BMW 228i xDrive Gran Coupe models from participating BMW Centers through BMW Financial Services NA, LLC, to customers who meet BMW Financial Services' credit requirements. Offer not valid in Puerto
            Rico. Monthly lease payments of $459 per month for 36 months is based on an adjusted capitalized cost of $36,155 (MSRP of $40,895, including destination and handling fee of $995, less $3,915 capitalized cost reduction, $0 security deposit,
            and suggested dealer contribution of $825). Actual MSRP and dealer contribution may vary and could affect your monthly lease payment. Cash due at signing includes $3,915 capitalized cost reduction, $459 first month's payment, $925 acquisition
            fee and $0 security deposit. Lessee responsible for insurance during the lease term, excess wear and tear as defined in the lease contract, $0.25/mile over 30,000 miles, plus disposition fee of up to $495 (not to exceed an amount permissible
            by law) at lease end. Not all customers will qualify for security deposit waiver. Tax, title, license, registration and dealer fees are additional fees due at signing. Advertised payment does not include applicable taxes. Purchase option at
            lease end, excluding tax, title and government fees, is $23,719. Offer valid through June 30, 2022 and may be combined with other offers unless otherwise stated. Models pictured may be shown with metallic paint and/or additional accessories.
            Visit your authorized BMW Center for important details.
          </p>
        </div>
      </section>
      <section class="slideCard" id="slide2" data-slide="2">
        <img class="bmwX3" alt="BMW X3" />
        <a class="disclosurePrompt" alt="Disclosure">Important Information</a>
        <div class="disclosureContainer">
          <i class="fa fa-times"></i>
          <p class="disclosureText">
            Through June 30, 2022, lease offer available on new 2022 BMW 228i xDrive Gran Coupe models from participating BMW Centers through BMW Financial Services NA, LLC, to customers who meet BMW Financial Services' credit requirements. Offer not valid in Puerto
            Rico. Monthly lease payments of $459 per month for 36 months is based on an adjusted capitalized cost of $36,155 (MSRP of $40,895, including destination and handling fee of $995, less $3,915 capitalized cost reduction, $0 security deposit,
            and suggested dealer contribution of $825). Actual MSRP and dealer contribution may vary and could affect your monthly lease payment. Cash due at signing includes $3,915 capitalized cost reduction, $459 first month's payment, $925 acquisition
            fee and $0 security deposit. Lessee responsible for insurance during the lease term, excess wear and tear as defined in the lease contract, $0.25/mile over 30,000 miles, plus disposition fee of up to $495 (not to exceed an amount permissible
            by law) at lease end. Not all customers will qualify for security deposit waiver. Tax, title, license, registration and dealer fees are additional fees due at signing. Advertised payment does not include applicable taxes. Purchase option at
            lease end, excluding tax, title and government fees, is $23,719. Offer valid through June 30, 2022 and may be combined with other offers unless otherwise stated. Models pictured may be shown with metallic paint and/or additional accessories.
            Visit your authorized BMW Center for important details.
          </p>
        </div>
      </section>
      <section class="slideCard" id="slide3" data-slide="3">
        <img class="bmwiX" alt="BMW iX" />
        <a class="disclosurePrompt" alt="Disclosure">Important Information</a>
        <div class="disclosureContainer">
          <i class="fa fa-times"></i>
          <p class="disclosureText">
            Through June 30, 2022, lease offer available on new 2022 BMW 228i xDrive Gran Coupe models from participating BMW Centers through BMW Financial Services NA, LLC, to customers who meet BMW Financial Services' credit requirements. Offer not valid in Puerto
            Rico. Monthly lease payments of $459 per month for 36 months is based on an adjusted capitalized cost of $36,155 (MSRP of $40,895, including destination and handling fee of $995, less $3,915 capitalized cost reduction, $0 security deposit,
            and suggested dealer contribution of $825). Actual MSRP and dealer contribution may vary and could affect your monthly lease payment. Cash due at signing includes $3,915 capitalized cost reduction, $459 first month's payment, $925 acquisition
            fee and $0 security deposit. Lessee responsible for insurance during the lease term, excess wear and tear as defined in the lease contract, $0.25/mile over 30,000 miles, plus disposition fee of up to $495 (not to exceed an amount permissible
            by law) at lease end. Not all customers will qualify for security deposit waiver. Tax, title, license, registration and dealer fees are additional fees due at signing. Advertised payment does not include applicable taxes. Purchase option at
            lease end, excluding tax, title and government fees, is $23,719. Offer valid through June 30, 2022 and may be combined with other offers unless otherwise stated. Models pictured may be shown with metallic paint and/or additional accessories.
            Visit your authorized BMW Center for important details.
          </p>
        </div>
      </section>
      <section class="slideCard" id="slide4" data-slide="4">
        <img class="bmw5series" alt="BMW 5 Series" />
        <a class="disclosurePrompt" alt="Disclosure">Important Information</a>
        <div class="disclosureContainer">
          <i class="fa fa-times"></i>
          <p class="disclosureText">
            Through June 30, 2022, lease offer available on new 2022 BMW 228i xDrive Gran Coupe models from participating BMW Centers through BMW Financial Services NA, LLC, to customers who meet BMW Financial Services' credit requirements. Offer not valid in Puerto
            Rico. Monthly lease payments of $459 per month for 36 months is based on an adjusted capitalized cost of $36,155 (MSRP of $40,895, including destination and handling fee of $995, less $3,915 capitalized cost reduction, $0 security deposit,
            and suggested dealer contribution of $825). Actual MSRP and dealer contribution may vary and could affect your monthly lease payment. Cash due at signing includes $3,915 capitalized cost reduction, $459 first month's payment, $925 acquisition
            fee and $0 security deposit. Lessee responsible for insurance during the lease term, excess wear and tear as defined in the lease contract, $0.25/mile over 30,000 miles, plus disposition fee of up to $495 (not to exceed an amount permissible
            by law) at lease end. Not all customers will qualify for security deposit waiver. Tax, title, license, registration and dealer fees are additional fees due at signing. Advertised payment does not include applicable taxes. Purchase option at
            lease end, excluding tax, title and government fees, is $23,719. Offer valid through June 30, 2022 and may be combined with other offers unless otherwise stated. Models pictured may be shown with metallic paint and/or additional accessories.
            Visit your authorized BMW Center for important details.
          </p>
        </div>
      </section>
      <section class="slideCard" id="slide5" data-slide="5">
        <img class="bmwPreOwned" alt="BMW Certified Pre-Owned" />
        <img id="bmwCPOLogo" />
        <a class="disclosurePrompt" alt="Disclosure">Important Information</a>
        <div class="disclosureContainer">
          <i class="fa fa-times"></i>
          <p class="disclosureText">
            Through May 15, 2022, lease offer available on new 2022 BMW 228i xDrive Gran Coupe models from participating BMW Centers through BMW Financial Services NA, LLC, to customers who meet BMW Financial Services' credit requirements. Offer not valid in Puerto
            Rico. Monthly lease payments of $459 per month for 36 months is based on an adjusted capitalized cost of $36,155 (MSRP of $40,895, including destination and handling fee of $995, less $3,915 capitalized cost reduction, $0 security deposit
            and suggested dealer contribution of $825). Actual MSRP and dealer contribution may vary and could affect your monthly lease payment. Cash due at signing includes $3,915 capitalized cost reduction, $459 first month's payment, $925 acquisition
            fee and $0 security deposit. Lessee responsible for insurance during the lease term, excess wear and tear as defined in the lease contract, $0.25/mile over 30,000 miles, plus disposition fee of up to $495 (not to exceed an amount permissible
            by law) at lease end. Not all customers will qualify for security deposit waiver. Tax, title, license, registration and dealer fees are additional fees due at signing. Advertised payment does not include applicable taxes. Purchase option at
            lease end, excluding tax, title and government fees, is $23,719. Offer valid through May 15, 2022 and may be combined with other offers unless otherwise stated. Models pictured may be shown with metallic paint and/or additional accessories.
            Visit your authorized BMW Center for important details.
          </p>
        </div>
      </section>
    </div>
  </div>
  </div>
</body>
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
AStombaugh
  • 2,930
  • 5
  • 13
  • 31
  • 1
    flex basis property is stopping slide show. We need to reset and set it again to make the slide show working. Regarding style error, Always keep in mind that when we are reading nodes using getByClassName or getByQuerySelector it gives array. We need to extract element on which we want to apply the style. For better visibility you may console your output to see the array and index position to access the element. – deepak Jun 24 '22 at 09:53

2 Answers2

6

Infinite JavaScript Carousel

  • Components should be reusable. Don't use IDs at all. Use Classes instead.
  • To make a component responsive, the simplest is to set to the main wrapper i.e: font-size: 3vmin; and define all sizes (font-size, widths, heights, positions, etc) for the descendants (children) in em units.
  • To highlight a bullet or slide as current target with JS an element by c (current) index and use someElement.classList.toggle("is-active", thisIndex === currentIndex)

Let's start with a minimal bare-bone responsive CSS:

.carousel {
  position: relative;
  overflow: hidden;
}

.carousel-slider {
  display: flex;
}

.carousel-slide {
  flex: 1 0 100%;
}

.carousel-slide img {
  display: block;
  width: 100%;
  height: 10em;
  object-fit: cover;
}

/* That's all. Other styles go here */
<div class="carousel">
  <div class="carousel-slider">
    <div class="carousel-slide"><img src="https://via.placeholder.com/800x350/0bf?text=image1" alt="Image 1"></div>
    <div class="carousel-slide"><img src="https://via.placeholder.com/800x350/fb0?text=image2" alt="Image 2"></div>
    <div class="carousel-slide"><img src="https://via.placeholder.com/800x350/b0f?text=image3" alt="Image 3"></div>
  </div>
</div>

That's all regarding CSS.
"No navigation bullets in HTML?" -you might ask. No, since such UI elements should be created dynamically by JavaScript. Not manually, since it's a daunting task.

Here's the simplified and improved JavaScript for the Carousel component:

// DOM utility functions:

const el = (sel, par) => (par || document).querySelector(sel);
const els = (sel, par) => (par || document).querySelectorAll(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);

// Helper functions:

const mod = (n, m) => (n % m + m) % m;

// Task: Carousel:

const carousel = (elCarousel) => {

  const animation = 500;
  const pause = 5000;
  // Or use something like: const animation = Math.abs(elCarousel.dataset.carouselAnimation ?? 500);

  const elCarouselSlider = el(".carousel-slider", elCarousel);
  const elsSlides = els(".carousel-slide", elCarouselSlider);
  const elsBtns = [];

  let itv; // Autoslide interval
  let tot = elsSlides.length; // Total slides
  let c = 0;

  if (tot < 2) return; // Not enough slides. Do nothing.

  // Methods:
  const anim = (ms = animation) => {
    const cMod = mod(c, tot);
    // Move slider
    elCarouselSlider.style.transitionDuration = `${ms}ms`;
    elCarouselSlider.style.transform = `translateX(${(-c - 1) * 100}%)`;
    // Handle active classes (slide and bullet)
    elsSlides.forEach((elSlide, i) => elSlide.classList.toggle("is-active", cMod === i));
    elsBtns.forEach((elBtn, i) => elBtn.classList.toggle("is-active", cMod === i));
  };

  const prev = () => {
    if (c <= -1) return; // prevent blanks on fast prev-click
    c -= 1;
    anim();
  };

  const next = () => {
    if (c >= tot) return; // prevent blanks on fast next-click
    c += 1;
    anim();
  };

  const goto = (index) => {
    c = index;
    anim();
  };

  const play = () => {
    itv = setInterval(next, pause + animation);
  };

  const stop = () => {
    clearInterval(itv);
  };

  // Buttons:

  const elPrev = elNew("button", {
    type: "button",
    className: "carousel-prev",
    innerHTML: "<span>Prev</span>",
    onclick: () => prev(),
  });

  const elNext = elNew("button", {
    type: "button",
    className: "carousel-next",
    innerHTML: "<span>Next</span>",
    onclick: () => next(),
  });

  // Navigation:

  const elNavigation = elNew("div", {
    className: "carousel-navigation",
  });

  // Navigation bullets:

  for (let i = 0; i < tot; i++) {
    const elBtn = elNew("button", {
      type: "button",
      className: "carousel-bullet",
      onclick: () => goto(i)
    });
    elsBtns.push(elBtn);
  }


  // Events:

  // Infinite slide effect:
  elCarouselSlider.addEventListener("transitionend", () => {
    if (c <= -1) c = tot - 1;
    if (c >= tot) c = 0;
    anim(0); // quickly switch to "c" slide (with animation duration 0)
  });

  // Pause on pointer enter:
  elCarousel.addEventListener("pointerenter", () => stop());
  elCarousel.addEventListener("pointerleave", () => play());

  // Init:

  // Insert UI elements:
  elNavigation.append(...elsBtns);
  elCarousel.append(elPrev, elNext, elNavigation);

  // Clone first and last slides (for "infinite" slider effect)
  elCarouselSlider.prepend(elsSlides[tot - 1].cloneNode(true));
  elCarouselSlider.append(elsSlides[0].cloneNode(true));

  // Initial slide
  anim();

  // Start autoplay
  play();
};

// Allows to use multiple carousels on the same page:
els(".carousel").forEach(carousel);
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.carousel {
  position: relative;
  overflow: hidden;
  font-size: 2.5vmin;
}

.carousel-slider {
  display: flex;
  transition: 0.3s;
}

.carousel-slide {
  flex: 1 0 100%;
}

.carousel-slide img {
  display: block;
  width: 100%;
  height: 36em;
  object-fit: cover;
}

.carousel button {
  font-size: inherit;
}

.carousel-prev,
.carousel-next {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  padding: 1em;
  border: none;
  cursor: pointer;
}

.carousel-prev {
  left: 2em;
}

.carousel-next {
  right: 2em;
}

.carousel-navigation {
  position: absolute;
  bottom: 1em;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  gap: 1em;
}

.carousel-bullet {
  width: 1em;
  height: 1em;
  border: none;
  background: #fff;
  cursor: pointer;
  border-radius: 50%;
}

.carousel-bullet.is-active {
  background: #1c69d3;
}
<div class="carousel">
  <div class="carousel-slider">
    <div class="carousel-slide"><img src="https://via.placeholder.com/800x350/0bf?text=image1" alt="Image 1"></div>
    <div class="carousel-slide"><img src="https://via.placeholder.com/800x350/fb0?text=image2" alt="Image 2"></div>
    <div class="carousel-slide"><img src="https://via.placeholder.com/800x350/b0f?text=image3" alt="Image 3"></div>
    <div class="carousel-slide"><img src="https://via.placeholder.com/800x350/0bf?text=image4" alt="Image 4"></div>
    <div class="carousel-slide"><img src="https://via.placeholder.com/800x350/0fb?text=image5" alt="Image 5"></div>
    <div class="carousel-slide"><img src="https://via.placeholder.com/800x350/f0b?text=image6" alt="Image 6"></div>
  </div>
</div>

The active state for slides and bullet buttons is handled by fixing the current slide index using Fix negative modulo operator %.

Thanks to the above els(".carousel").forEach(carousel); and the use of classes — you can have indefinite carousels inside a single page - with each one working independently from the other.

Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • 2
    Thank you! It'll probably take me some time to integrate with my project but your answer was clear and your example works so I gave you the bounty before it expired in a few minutes. :) – AStombaugh Jul 15 '22 at 12:05
  • 2
    You're welcome. Let me know if you run into integration troubles. – Roko C. Buljan Jul 15 '22 at 12:52
0

I have corrected the code. It seems issue in flex property and issue while accessing node array.I have noted down all the issues.

  1. style apply in slideCard divs flex: 0 0 100%. flex basis 100% will expand all the images to take 100% width. We need to reset all to 0 and make the currentSlide to take complete width when we are changing the slides with arrow .

  2. When we access nodes using getClassByName.It always return the array.

var slides = document.querySelectorAll(".slideCard");
var slideDots = document.querySelectorAll(".slideDot");
let currentSlide = 0;
let currentArrow = null;
let previousArrow = null;

function resetDot() {
  for (let i = 0; i < slideDots.length; i++) {
    slideDots[i].classList.remove("selectedSlide");
  }
}

function slideMovement(direction) {
  if (direction === "right") {
    for (let i = 0; i < slides.length; i++) {
      slides[i].style.flex = "0";
      slides[i].style.transform = "translateX(-100%)";
    }
  } else {
    for (let i = 0; i < slides.length; i++) {
      slides[i].style.flex = "0";
      slides[i].style.transform = "translateX(100%)";
    }
  }
  resetDot();
}

const updateSlideStyle = (index) => {
  slides[index].style.flex = "1 1 100%";
  slides[index].style.transition = "transform 0.6s linear";
  if (previousArrow != currentArrow) {
    setTimeout(() => {
      slides[index].style.transform = "translateX(0px)";
      previousArrow = currentArrow;
    }, 600);
  } else {
    slides[index].style.transform = "translateX(0px)";
  }
};

const slideEvent = (direction) => {
  const directionMovStep = direction === "right" ? 1 : -1;
  arrowClicked = directionMovStep;
  changeSlides(directionMovStep);
  updateSlideStyle(currentSlide);
  //Dots handle
  slideDots[currentSlide].classList.add("selectedSlide");
};

function setCurrentSlide(n) {
  if (n < 0) {
    currentSlide = slides.length - 1;
  }
  if (n >= slides.length) {
    currentSlide = 0;
  }
}

function changeSlides(n) {
  const direction = n >= 0 ? "right" : "left";
  currentArrow = direction;
  setCurrentSlide((currentSlide += n));
  slideMovement(direction);
  let discWindows = document.querySelectorAll(".discVisible");
  [].forEach.call(discWindows, function(el) {
    el.classList.remove("discVisible");
  });
}

//open disclosure
let discBtn = document.getElementsByClassName("disclosurePrompt");
let disc;
for (disc = 0; disc < discBtn.length - 1; disc++) {
  discBtn[disc].addEventListener("click", function() {
    this.nextElementSibling.classList.add("discVisible");
  });
}

// //manually select slide
let slideSelectBtn = document.getElementsByClassName("slideNumberDots");
slideSelectBtn[0].addEventListener("click", function(event) {
  const {
    target
  } = event;
  if (target) {
    currentSlide = target.getAttribute("data-slide") - 1;
    slideMovement();
    updateSlideStyle(currentSlide);
    target.classList.add("selectedSlide");
  }
});

//close disclosure
let closeBtn = document.getElementsByClassName("fa-times");
let close;
for (close = 0; close < closeBtn.length - 1; close++) {
  closeBtn[close].addEventListener("click", function() {
    var slideDiscWindow = document.querySelectorAll(".discVisible");
    [].forEach.call(slideDiscWindow, function(el) {
      el.classList.remove("discVisible");
    });
  });
}

//slide controls

let left = document.getElementById("slideLeft");
let right = document.getElementById("slideRight");

left.addEventListener("click", () => slideEvent("left"));
right.addEventListener("click", () => slideEvent("right"));
slideDots[currentSlide].classList.add("selectedSlide");
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  background-color: darkgrey;
}

html {
  font-size: 16px;
}


/* begin slideshow layout */

.slideShow {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  background-color: #414141;
  position: relative;
  width: 100%;
  height: 40vw;
  margin: 0 auto;
  padding: 0;
  /*overflow: hidden; */
  box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 1px 3px 1px;
}

.slidesContainer {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  background-color: #414141;
  position: relative;
  width: 100%;
  height: 40vw;
  margin: 0 auto;
  padding: 0;
}

.slideCard {
  display: flex;
  flex-direction: row;
  position: relative;
  height: 100%;
  width: 100%;
  margin: 0px;
  padding: 0;
  background-color: transparent;
  flex: 1 0 100%;
}

.fa-chevron-right {
  display: block;
  opacity: 0;
  font-size: 2.3vw;
  position: absolute;
  top: 50%;
  right: 0;
  color: white;
  margin: 0 5%;
  padding: 0;
  width: auto;
  height: auto;
  z-index: 1;
  background-color: transparent;
  cursor: pointer;
  transform-origin: center;
  transition: transform 0.15s linear, opacity 0.15s linear;
}

.fa-chevron-left {
  display: block;
  opacity: 0;
  font-size: 2.3vw;
  position: absolute;
  top: 50%;
  left: 0;
  color: white;
  margin: 0 5%;
  padding: 0;
  width: auto;
  height: auto;
  z-index: 1;
  background-color: transparent;
  cursor: pointer;
  transition: transform 0.15s linear, opacity 0.15s linear;
}

.fa-chevron-right:hover {
  transform: scale(1.2);
}

.fa-chevron-left:hover {
  transform: scale(1.2);
}

.slideShow:hover .fa-chevron-right {
  opacity: 1;
}

.slideShow:hover .fa-chevron-left {
  opacity: 1;
}

.slideNumberDots {
  display: flex;
  flex-direction: row;
  justify-content: center;
  bottom: 0%;
  gap: 0.8vw;
  position: absolute;
  width: 100%;
  z-index: 1;
  margin: 0 auto;
  padding: 1vw;
  background-color: transparent;
  pointer-events: none;
}

.slideDot {
  display: flex;
  height: 0.8vw;
  width: 0.8vw;
  border-radius: 60%;
  border: 0px solid rgb(27, 27, 27);
  margin: 0;
  padding: 0;
  background-color: white;
  transform-origin: center;
  transition: transform 0.2s linear, background-color 0.2s linear;
  pointer-events: all;
}

.slideDot:hover {
  background-color: #1c69d3;
  transform-origin: center;
  transform: scale(1.3);
  cursor: pointer;
}

.slideDot.selectedSlide {
  background-color: #1c69d3;
  transform: scale(1.2);
  transform-origin: center;
  transition: color, transform 0.3s linear;
  outline: 0.15vw solid black;
  border-radius: 50%;
}

.disclosurePrompt {
  display: flex;
  font-family: BMWTypeNext Latin TT, Helvetica, Arial, sans-serif;
  position: absolute;
  color: white;
  font-size: 0.8vw;
  font-weight: 400;
  line-height: 1.25;
  width: fit-content;
  height: fit-content;
  top: 95%;
  left: 5%;
  cursor: pointer;
  z-index: 2;
  user-select: none;
  outline: 1px transparent;
  text-decoration: underline;
}

.disclosurePrompt:hover {
  color: #e4e4e4;
}

.disclosurePrompt:focus {
  color: #e4e4e4;
}

.disclosureContainer {
  visibility: hidden;
  width: 90vw;
  height: auto;
  outline: 1px solid black;
  background-color: rgba(0, 0, 0, 0.95);
  position: absolute;
  margin: 0 auto;
  bottom: 5%;
  left: 5%;
  opacity: 0;
  z-index: 10;
  transition: opacity, top, 0.3s linear;
}

.disclosureContainer.discVisible {
  visibility: visible;
  bottom: 10.5%;
  opacity: 1;
}

.disclosureText {
  font-family: BMWTypeNext Latin TT, Helvetica, Arial, sans-serif;
  color: white;
  line-height: clamp(0.7rem, -0.6rem + 3vw, 0.9rem);
  font-size: clamp(0.5rem, -0.875rem + 3vw, 0.7rem);
  display: block;
  margin: 0 auto;
  padding: 1.5rem 0.5rem 0.5rem 0.5rem;
  text-align: justify;
}

.fa-times {
  display: block;
  color: white;
  font-size: 0.8em;
  position: absolute;
  left: 0;
  top: 0;
  z-index: 12;
  padding: 0.5rem 0.5rem;
  transition: all 0.2s linear;
  cursor: pointer;
}

.fa-times:hover {
  color: #1c69d3;
  transition: all 0.2s linear;
}


/* end slideshow layout */


/* begin animations */

.backInLeft {
  animation-name: backInLeft;
  animation-duration: 0.7s;
}

@keyframes backInLeft {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(0px);
  }
}

.backInRight {
  animation-name: backInRight;
  animation-duration: 0.7s;
}

@keyframes backInRight {
  0% {
    transform: translateX(100%);
  }
  100% {
    transform: translateX(0px);
  }
}


/* end animations */


/* begin images */

#bmw2series {
  content: url("https://i.imgur.com/MABHqGy.jpg");
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}

#bmw3series {
  content: url("https://i.imgur.com/Ggy6iNU.jpg");
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}

#bmwX3 {
  content: url("https://i.imgur.com/ucYCFcu.jpg");
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}

#bmwiX {
  content: url("https://i.imgur.com/bQhvuOY.jpg");
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}

#bmw5series {
  content: url("https://i.imgur.com/sLYH9Gy.jpg");
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}

#bmwPreOwned {
  content: url("https://i.imgur.com/kuOWIEJ.jpg");
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
}
<html>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous" referrerpolicy="no-referrer"
/>

<body>
  <!--master container-->
  <div class="slideShow">
    <!--slideshow controls-->
    <section id="controls">
      <a><i id="slideRight" class="fa fa-chevron-right"></i></a>
      <a><i id="slideLeft" class="fa fa-chevron-left"></i></a>
      <div class="slideNumberDots">
        <a id="slideDot1" data-slide="1" class="slideDot"></a>
        <a id="slideDot2" data-slide="2" class="slideDot"></a>
        <a id="slideDot3" data-slide="3" class="slideDot"></a>
        <a id="slideDot4" data-slide="4" class="slideDot"></a>
        <a id="slideDot5" data-slide="5" class="slideDot"></a>
        <a id="slideDot6" data-slide="6" class="slideDot"></a>
      </div>
    </section>
    <!--slideshow container-->
    <div class="slidesContainer">
      <!--slide 1-->
      <div class="slideCard" id="slide1" value="2series">
        <img id="bmw2series" />
        <a class="disclosurePrompt" alt="Disclosure">Important Information</a>
        <div class="disclosureContainer">
          <i class="fa fa-times"></i>
          <p class="disclosureText">
            Through May 15, 2022, lease offer available on new 2022 BMW 228i xDrive Gran Coupe models from participating BMW Centers through BMW Financial Services NA, LLC, to customers who meet BMW Financial Services' credit requirements.
          </p>
        </div>
      </div>
      <!--slide 2-->
      <div class="slideCard" id="slide2" value="3series">
        <img id="bmw3series" />
        <a class="disclosurePrompt" alt="Disclosure">Important Information</a>
        <div class="disclosureContainer">
          <i class="fa fa-times"></i>
          <p class="disclosureText">
            Through May 15, 2022, lease offer available on new 2022 BMW 228i xDrive Gran Coupe models from participating BMW Centers through BMW Financial Services NA, LLC, to customers who meet BMW Financial Services' credit requirements.
          </p>
        </div>
      </div>
      <!--slide 3-->
      <div class="slideCard" id="slide3" value="X3">
        <img id="bmwX3" />
        <a class="disclosurePrompt" alt="Disclosure">Important Information</a>
        <div class="disclosureContainer">
          <i class="fa fa-times"></i>
          <p class="disclosureText">
            Through May 15, 2022, lease offer available on new 2022 BMW 228i xDrive Gran Coupe models from participating BMW Centers through BMW Financial Services NA, LLC, to customers who meet BMW Financial Services' credit requirements.
          </p>
        </div>
      </div>
      <!--slide 4-->
      <div class="slideCard" id="slide4" value="iX">
        <img id="bmwiX" />
        <a class="disclosurePrompt" alt="Disclosure">Important Information</a>
        <div class="disclosureContainer">
          <i class="fa fa-times"></i>
          <p class="disclosureText">
            Through May 15, 2022, lease offer available on new 2022 BMW 228i xDrive Gran Coupe models from participating BMW Centers through BMW Financial Services NA, LLC, to customers who meet BMW Financial Services' credit requirements.
          </p>
        </div>
      </div>
      <!--slide 5-->
      <div class="slideCard" id="slide5" value="5series">
        <img id="bmw5series" />
        <a class="disclosurePrompt" alt="Disclosure">Important Information</a>
        <div class="disclosureContainer">
          <i class="fa fa-times"></i>
          <p class="disclosureText">
            Through May 15, 2022, lease offer available on new 2022 BMW 228i xDrive Gran Coupe models from participating BMW Centers through BMW Financial Services NA, LLC, to customers who meet BMW Financial Services' credit requirements.
          </p>
        </div>
      </div>
      <!--slide 6-->
      <div class="slideCard" id="slide6" value="preOwned1">
        <img id="bmwPreOwned" />
        <a class="disclosurePrompt" alt="Disclosure">Important Information</a>
        <div class="disclosureContainer">
          <i class="fa fa-times"></i>
          <p class="disclosureText">
            Through May 15, 2022, lease offer available on new 2022 BMW 228i xDrive Gran Coupe models from participating BMW Centers through BMW Financial Services NA, LLC, to customers who meet BMW Financial Services' credit requirements.
          </p>
        </div>
      </div>
    </div>
  </div>
</body>

</html>
deepak
  • 1,390
  • 1
  • 8
  • 12
  • Have a look at the errors being produced. For example you have slides.style but slides is a collection of elements, not a single element. The errors are shown if you run the snippet, or use your browser dev tools inspect facility to find them. – A Haworth Jun 20 '22 at 17:18
  • @AHaworth I'm not getting any errors on the dots, or anything at all, which is why I'm completely lost as to why they don't function. I included the error in the original question. For the slides and the transform/transition, what would be a better way of defining them? I tried using `getElementsByClassName` but still gives me the same error. The slides all have ids assigned, could I collect the ids into an array and have the transform functions treat each id the same way? I would have thought querySelectorAll was the way to go here but like I said in the question...out of my depth. :| – AStombaugh Jun 20 '22 at 17:24
  • Aologies, my cmment should have been under the question not under this answer. – A Haworth Jun 20 '22 at 17:28
  • @AStombaugh - Hope it will solve the issue. I modified the css and html and some event binding to optimize the code. – deepak Jun 24 '22 at 09:50
  • @deepak Almost! You've gotten much further than I did! I was playing with `.scrollIntoView` yesterday and it almost worked but if the user expanded the screen the other slides became visible as though it was breaking the overflow attribute. There are a couple of issues with what you gave me: There seems to be some kind of "bounce" back effect happening on the slides if you try to go forward or backward, almost like it's over-transforming and then coming back into view. Do you know what might be causing that? – AStombaugh Jun 24 '22 at 11:03
  • @deepak The other issue is that I'm trying to get the slides to flow one to the other without the background appearing which is why I was using the flexbox with overflow so it was essentially scrolling the container to the next slide. Here's an example of what I mean: https://www.ramtrucks.com/ except they're actually transforming the entire container and not just individual slides (which I tried to figure out but wasn't able to). Thank you for working on this with me! I greatly appreciate your efforts! You've got me a lot closer than when I originally asked the question! – AStombaugh Jun 24 '22 at 11:04
  • @deepak It's almost as if it's retaining the translation and transitions on change so if you try to go backwards it translates but does the initial translate first and then translates it again. I wonder if there's a way to reset the translates. Or, since there's X number of slides in the same container, I wonder if I could translate them in growing percentages or vw. So, slide 1 is 0, slide 2 is 100, slide 3 is 200, etc. – AStombaugh Jun 24 '22 at 11:21
  • Regarding bouncing effect, it is due to translation issue. – deepak Jun 24 '22 at 14:13
  • @deepak Do you know if there's a way to clear the styling after the slide has transitioned? Could we do it in a class that's applied to the active slide and then just cycle through them instead of applying inline style that stay's with the slide after the transforms have done their job? I'll keep playing with it – AStombaugh Jun 25 '22 at 13:34