skip to Main Content

I’m trying to put together a simple full width carousel that endlessly loops through the cards. The issue is, I’m not sure how to get it endlessly looping the cards. Currently, I’ve only been able to get as far as scrolling back to the original card after hitting the last one instead of being able to scroll backward and forward to the first card/last card again infinitely.

Can anyone provide any insight?

HTML:

<div class="carousel-container">
  <div class="carousel-wrapper">
    <div class="carousel">
      <div class="carousel-card">Card 1</div>
      <div class="carousel-card">Card 2</div>
      <div class="carousel-card">Card 3</div>
      <div class="carousel-card">Card 4</div>
      <div class="carousel-card">Card 5</div>
      <div class="carousel-card">Card 6</div>
    </div>
  </div>
  <div class="carousel-controls">
    <button id="prev">Back</button>
    <button id="next">Forward</button>
  </div>
</div>

CSS:

.carousel-container {
  width: 100%;
  margin: 0 auto;
  position: relative;
  overflow: hidden;
}

.carousel-wrapper {
  overflow: hidden;
  width: 100%;
}

.carousel {
  display: flex;
  transition: transform 0.5s ease-in-out;
  will-change: transform;
  gap: 60px;
}

.carousel-card {
  flex: 0 0 25%;
  background: #ff6b6b;
  border-radius: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 1.5rem;
  color: white;
  text-align: center;
  min-height: 200px;
  box-sizing: border-box;
  padding: 10px;
}

.carousel-controls {
  text-align: center;
  margin-top: 20px;
}

.carousel-controls button {
  padding: 10px 20px;
  font-size: 1rem;
  border: none;
  background: #4ecdc4;
  color: white;
  border-radius: 5px;
  cursor: pointer;
  margin: 0 10px;
  transition: background 0.3s ease;
}

.carousel-controls button:hover {
  background: #358f89;
}

jQuery:

jQuery(document).ready(function () {
  const $carousel = jQuery('.carousel');
  const $carouselWrapper = jQuery('.carousel-wrapper');
  const $carouselCards = jQuery('.carousel-card');
  const cardCount = $carouselCards.length; // Number of original cards
  let currentIndex = 0; // Track current visible card index
  const cardWidth = $carouselCards.outerWidth(true); // Width including margins
  const totalCardCount = cardCount; // Only the original set of cards (no cloning)

  // Function to center the active card in the middle of the container
  function centerActiveCard() {
    const wrapperWidth = $carouselWrapper.innerWidth();
    const visibleWidth = wrapperWidth / 2;
    const offset = -(cardWidth + 60) * currentIndex + visibleWidth - cardWidth / 2;
    $carousel.css('transform', `translateX(${offset}px)`);
  }

  centerActiveCard();

  // Function to scroll left (Previous button)
  function scrollLeft() {
    currentIndex--;
    if (currentIndex < 0) {
      currentIndex = cardCount - 1; // Loop back to the last card of the set
    }
    centerActiveCard();
  }

  // Function to scroll right (Next button)
  function scrollRight() {
    currentIndex++;
    if (currentIndex >= cardCount) {
      currentIndex = 0; // Loop back to the first card of the set
    }
    centerActiveCard();
  }

  // Event listeners for navigation buttons
  jQuery('#next').on('click', scrollRight);
  jQuery('#prev').on('click', scrollLeft);

  // Responsive adjustments
  jQuery(window).on('resize', function () {
    centerActiveCard();
  });
});

Here’s a CodePen so you can see what I’ve got so far:

https://codepen.io/Paul-Moignard/pen/xbKzBNe

Thanks in advance for your help!

2

Answers


  1. Summary of Problems and Fixes
    Issue
    Problem in Original Code

    Card Cloning
    No clones were created, causing breaks in scrolling. Added clones using clone() and adjusted starting positions accordingly.
    Transition Reset
    No logic to reset position after reaching clones. Used transitionend to reset positions when scrolling past the cloned cards.
    Card Centering Offset calculation did not account for clones. Adjusted centerActiveCard() to use the width of the cloned cards for positioning.
    Responsive Behavior No recalculation on window resize. Added resize event listener to recalculate offsets dynamically when the screen size changes.

    Here is an updated jquery code

    jQuery(document).ready(function () {
      const $carousel = jQuery('.carousel');
      const $carouselWrapper = jQuery('.carousel-wrapper');
      const $carouselCards = jQuery('.carousel-card');
      const cardCount = $carouselCards.length; // Original number of cards
      const cardWidth = $carouselCards.outerWidth(true); // Width of a card (including gap)
      let currentIndex = cardCount; // Start at the first real card
      let isTransitioning = false;
    
      // **1. Clone the cards for seamless looping**
      const $firstClone = $carouselCards.clone();
      const $lastClone = $carouselCards.clone();
      $carousel.append($firstClone); // Append clones at the end
      $carousel.prepend($lastClone); // Prepend clones at the beginning
    
      // Adjust carousel's width to fit all cards (original + clones)
      const totalCardCount = $carousel.children().length;
      $carousel.css('width', `${totalCardCount * cardWidth}px`);
    
      // **2. Set the initial position to the first real card**
      function setInitialPosition() {
        const initialOffset = -cardWidth * cardCount; // Start at the first real card
        $carousel.css('transform', `translateX(${initialOffset}px)`);
      }
      setInitialPosition();
    
      // **3. Center the active card and handle scrolling**
      function centerActiveCard() {
        const offset = -(cardWidth * currentIndex);
        $carousel.css('transform', `translateX(${offset}px)`);
        isTransitioning = true;
        setTimeout(() => (isTransitioning = false), 500); // Wait for the transition to complete
      }
    
      // **4. Reset position after reaching cloned cards**
      function handleTransitionEnd() {
        if (currentIndex === 0) {
          // Jump to the last real card
          currentIndex = cardCount;
          $carousel.css('transition', 'none');
          centerActiveCard();
        } else if (currentIndex === totalCardCount - 1) {
          // Jump to the first real card
          currentIndex = cardCount - 1;
          $carousel.css('transition', 'none');
          centerActiveCard();
        } else {
          // Restore normal transition
          $carousel.css('transition', 'transform 0.5s ease-in-out');
        }
      }
    
      $carousel.on('transitionend', handleTransitionEnd);
    
      // **5. Scroll left (Previous button)**
      function scrollLeft() {
        if (isTransitioning) return;
        currentIndex--;
        centerActiveCard();
      }
    
      // **6. Scroll right (Next button)**
      function scrollRight() {
        if (isTransitioning) return;
        currentIndex++;
        centerActiveCard();
      }
    
      // **7. Event listeners for navigation buttons**
      jQuery('#next').on('click', scrollRight);
      jQuery('#prev').on('click', scrollLeft);
    
      // **8. Recalculate offsets on window resize**
      jQuery(window).on('resize', function () {
        centerActiveCard();
      });
    });
    
    Login or Signup to reply.
  2. The issue you’re experiencing likely stems from how the width of the carousel is calculated and how the cloned cards are being appended. Specifically, the cards may not be respecting their original flex: 0 0 25% CSS rule because the parent carousel container’s width or gap handling is not correctly aligning with the expected layout. I’ll adjust the CSS and jQuery to fix the positioning and alignment issues.

    Here’s the updated CSS with adjustments for consistent card widths and proper alignment:

    .carousel-container {
      width: 100%;
      margin: 0 auto;
      position: relative;
      overflow: hidden;
    }
    
    .carousel-wrapper {
      overflow: hidden;
      width: 100%;
    }
    
    .carousel {
      display: flex;
      transition: transform 0.5s ease-in-out;
      will-change: transform;
      gap: 20px; /* Adjusted to make gaps consistent */
    }
    
    .carousel-card {
      flex: 0 0 calc(25% - 20px); /* 25% of width minus gap */
      background: #ff6b6b;
      border-radius: 10px;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 1.5rem;
      color: white;
      text-align: center;
      min-height: 200px;
      box-sizing: border-box;
      padding: 10px;
    }
    
    .carousel-controls {
      text-align: center;
      margin-top: 20px;
    }
    
    .carousel-controls button {
      padding: 10px 20px;
      font-size: 1rem;
      border: none;
      background: #4ecdc4;
      color: white;
      border-radius: 5px;
      cursor: pointer;
      margin: 0 10px;
      transition: background 0.3s ease;
    }
    
    .carousel-controls button:hover {
      background: #358f89;
    }
    

    and this is adjusted Jquery

    jQuery(document).ready(function () {
      const $carousel = jQuery('.carousel');
      const $carouselWrapper = jQuery('.carousel-wrapper');
      const $carouselCards = jQuery('.carousel-card');
      const cardCount = $carouselCards.length; // Original number of cards
      let cardWidth = $carouselCards.outerWidth(true); // Dynamic width calculation
      let currentIndex = cardCount; // Start at the first real card
      let isTransitioning = false;
    
      // Clone the cards for seamless looping
      const $firstClone = $carouselCards.clone();
      const $lastClone = $carouselCards.clone();
      $carousel.append($firstClone);
      $carousel.prepend($lastClone);
    
      // Adjust carousel's width dynamically
      function setCarouselWidth() {
        cardWidth = $carouselCards.outerWidth(true); // Recalculate width
        const totalCardCount = $carousel.children().length;
        $carousel.css('width', `${totalCardCount * cardWidth}px`);
      }
      setCarouselWidth();
    
      // Position the carousel at the first real card
      function setInitialPosition() {
        const initialOffset = -cardWidth * cardCount;
        $carousel.css('transform', `translateX(${initialOffset}px)`);
      }
      setInitialPosition();
    
      // Center the active card and handle scrolling
      function centerActiveCard() {
        const offset = -(cardWidth * currentIndex);
        $carousel.css('transform', `translateX(${offset}px)`);
        isTransitioning = true;
        setTimeout(() => (isTransitioning = false), 500); // Wait for the transition to complete
      }
    
      // Reset position after reaching cloned cards
      function handleTransitionEnd() {
        if (currentIndex === 0) {
          currentIndex = cardCount;
          $carousel.css('transition', 'none');
          centerActiveCard();
        } else if (currentIndex === $carousel.children().length - 1) {
          currentIndex = cardCount - 1;
          $carousel.css('transition', 'none');
          centerActiveCard();
        } else {
          $carousel.css('transition', 'transform 0.5s ease-in-out');
        }
      }
    
      $carousel.on('transitionend', handleTransitionEnd);
    
      // Scroll left (Previous button)
      function scrollLeft() {
        if (isTransitioning) return;
        currentIndex--;
        centerActiveCard();
      }
    
      // Scroll right (Next button)
      function scrollRight() {
        if (isTransitioning) return;
        currentIndex++;
        centerActiveCard();
      }
    
      // Event listeners for navigation buttons
      jQuery('#next').on('click', scrollRight);
      jQuery('#prev').on('click', scrollLeft);
    
      // Recalculate dimensions and offsets on window resize
      jQuery(window).on('resize', function () {
        setCarouselWidth();
        setInitialPosition();
        centerActiveCard();
      });
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search