Free Web Design Code & Scripts

Bootstrap 5 Carousel Multiple Items Per Slide

Bootstrap 5 Carousel Multiple Items Per Slide
Code Snippet:Bootstrap 5 Carousel with Multiple Items
Author: Henrique Baldy
Published: 9 months ago
Last Updated: 8 months ago
Downloads: 1,232
License: MIT
Edit Code online: View on CodePen
Read More

This code builds a Bootstrap 5 Carousel Multiple Items Per Slide with smooth navigation and responsive design. It shows several items together, making content display more engaging. Moreover, it includes infinite looping and auto-play for a seamless experience. Users can also drag or use keyboard controls for navigation. As a result, it is helpful for showcasing products, images, or portfolios in an interactive way.

How to Create Bootstrap 5 Carousel Multiple Items Per Slide

First of all, load the following assets into the head tag of your HTML document.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css">

Create the HTML structure as follows:

<div class="container-fluid">
  <div class="heading mt-3 mb-3">
    <h2 class="text-center" id="carouselTitle">Bootstrap 5 Carousel with Multiple Items</h2>
    <p>© Image credits: <a href="https://piclumen.com/app/user/social-home/241774" target="_blank">bro-dee / PicLumen</a></p>
  </div>

  <div class="multi-carousel-container" id="multiCarousel">
    <div class="multi-carousel-inner" id="carouselInner">
      <!-- Original items only -->
      <div class="multi-carousel-item" data-index="0">
        <div class="img-container"><span class="item-number">1</span>
          <img src="https://images.piclumen.com/community/20250328/11/453409ca-ef19-4d4e-b5fc-c3511ca65ee8.webp" alt="Image 1">
        </div>
      </div>
      <div class="multi-carousel-item" data-index="1">
        <div class="img-container"><span class="item-number">2</span>
          <img src="https://images.piclumen.com/community/20250401/11/62637881-6bb6-48a8-883d-0fd9d76f4a08.webp" alt="Image 2">
        </div>
      </div>
      <div class="multi-carousel-item" data-index="2">
        <div class="img-container"><span class="item-number">3</span>
          <img src="https://images.piclumen.com/community/20250401/11/50e0e11a-37b8-4a70-8d50-e394a46b633c.webp" alt="Image 3">
        </div>
      </div>
      <div class="multi-carousel-item" data-index="3">
        <div class="img-container"><span class="item-number">4</span>
          <img src="https://images.piclumen.com/community/20250331/10/d54603ee-2f13-491f-bc14-1cd548afb389.webp" alt="Image 4">
        </div>
      </div>
      <div class="multi-carousel-item" data-index="4">
        <div class="img-container"><span class="item-number">5</span>
          <img src="https://images.piclumen.com/community/20250327/10/1d2cd467-5e74-4b24-b668-16980a6009ff.webp" alt="Image 5">
        </div>
      </div>
      <div class="multi-carousel-item" data-index="5">
        <div class="img-container"><span class="item-number">6</span>
          <img src="https://images.piclumen.com/community/20250326/14/a29c1ea3-d174-47b4-b7b4-9c9e5af80ea8.webp" alt="Image 6">
        </div>
      </div>
      <div class="multi-carousel-item" data-index="6">
        <div class="img-container"><span class="item-number">7</span>
          <img src="https://images.piclumen.com/community/20250326/11/2871590f-5896-4824-8f27-00ebb20e9bd3.webp" alt="Image 7">
        </div>
      </div>
      <div class="multi-carousel-item" data-index="7">
        <div class="img-container"><span class="item-number">8</span>
          <img src="https://images.piclumen.com/community/20250326/11/a8638ee6-5768-4b9f-87c1-bdebdc78b597.webp" alt="Image 8">
        </div>
      </div>
      <div class="multi-carousel-item" data-index="8">
        <div class="img-container"><span class="item-number">9</span>
          <img src="https://images.piclumen.com/community/20250325/13/a70ce527-4c30-45f2-8fcd-8c80f3b86a7c.webp" alt="Image 9">
        </div>
      </div>
    </div>

    <button class="multi-carousel-control-prev" id="prevBtn">
      <span class="carousel-control-prev-icon" aria-hidden="true"><</span>
    </button>
    <button class="multi-carousel-control-next" id="nextBtn">
      <span class="carousel-control-next-icon" aria-hidden="true">></span>
    </button>
  </div>
</div>

Style using the following CSS styles:

/* Carousel heading */
.heading {
  display: block;
  text-align: center;
}


/* Container for the entire carousel */
.multi-carousel-container {
  cursor: grab;
  margin: 0 auto;
  max-width: 100%;
  overflow: hidden;
  position: relative;
}

/* Cursor styles for dragging */
.multi-carousel-container.dragging,
#multiCarousel.dragging {
  cursor: grabbing;
}

/* Wrapper for all slides */
.multi-carousel-inner {
  display: flex;
  transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* Individual slide */
.multi-carousel-item,
.clone {
  box-sizing: border-box;
  flex: 0 0 33.333333%;
  padding: 0 5px;
  position: relative; /* Essential for item-number positioning */
}

/* Control buttons */
.multi-carousel-control-prev,
.multi-carousel-control-next {
  align-items: center;
  background-color: rgba(0, 0, 0, 0.5);
  border: none;
  border-radius: 50%;
  color: white;
  cursor: pointer;
  display: flex;
  height: 40px;
  justify-content: center;
  position: absolute;
  text-decoration: none;
  top: 50%;
  transform: translateY(-50%);
  transition: background-color 0.3s ease;
  width: 40px;
  z-index: 10;
}

.multi-carousel-control-prev:hover,
.multi-carousel-control-next:hover {
  background-color: rgba(0, 0, 0, 0.7);
}

.multi-carousel-control-prev {
  left: 10px;
}

.multi-carousel-control-next {
  right: 10px;
}

/* Image container with dynamic height */
.img-container {
  border-radius: 1.5rem;
  height: var(--carousel-height, 80vh);
  overflow: hidden;
  position: relative;
}

/* Image styling */
.img-container img,
#carouselInner img {
  height: 100%;
  object-fit: cover;
  object-position: top;
  pointer-events: none;
  user-drag: none;
  width: 100%;
  -webkit-user-drag: none;
  transition: transform 0.3s ease;
}

.img-container:hover img {
  transform: translateZ(0) scale(1.02);
}

/* Item number styling - guaranteed visibility */
.item-number {
  align-items: center;
  background-color: rgba(255, 255, 255, 0.75);
  border-radius: 50%;
  display: inline-flex;
  font-size: 120%;
  font-weight: bold;
  height: 35px;
  justify-content: center;
  left: 1rem;
  position: absolute;
  top: 1rem;
  width: 35px;
  z-index: 2; /* Higher than default but below controls */
  /* Isolation prevents z-index context issues */
  isolation: isolate;
}

/* Carousel cursor styling */
#multiCarousel {
  cursor: grab;
  touch-action: pan-y;
}

/* Disable text selection during drag */
#multiCarousel.dragging {
  user-select: none;
  -webkit-user-select: none;
}

/* Responsive adjustments for screens smaller than 720px (45em) */
@media (max-width: 45em) {
  .multi-carousel-item,
  .clone {
    flex: 0 0 100%;
  }

Load the following scripts before closing the body tag:

<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js"></script>

Add the following JavaScript function:

document.addEventListener("DOMContentLoaded", function () {
  // Configuration - dynamic based on screen size
  let itemsPerSlide = window.innerWidth < 720 ? 1 : 3; // Responsive items per slide
  const totalItems = 9; // Total real items (without clones)
  let slideBy = window.innerWidth < 720 ? 1 : 1; // How many items to advance/retreat per click

  // DOM elements
  const carousel = document.getElementById("multiCarousel");
  const carouselInner = document.getElementById("carouselInner");
  const prevBtn = document.getElementById("prevBtn");
  const nextBtn = document.getElementById("nextBtn");

  // Function to update configuration based on screen size
  function updateConfig() {
    const isMobile = window.innerWidth < 720;
    itemsPerSlide = isMobile ? 1 : 3;
    slideBy = isMobile ? 1 : 1;
  }

  // Dynamically add clone elements
  function initializeClones() {
    const originalItems = Array.from(
      document.querySelectorAll(".multi-carousel-item:not(.clone)")
    );

    // Clear existing clones
    document.querySelectorAll(".clone").forEach((clone) => clone.remove());

    // Prepend clones of last items
    const lastClones = originalItems
      .slice(-itemsPerSlide)
      .map((item) => {
        const clone = item.cloneNode(true);
        clone.classList.add("clone");
        return clone;
      })
      .reverse();
    lastClones.forEach((clone) => carouselInner.prepend(clone));

    // Append clones of first items
    const firstClones = originalItems.slice(0, itemsPerSlide).map((item) => {
      const clone = item.cloneNode(true);
      clone.classList.add("clone");
      return clone;
    });
    firstClones.forEach((clone) => carouselInner.append(clone));
  }

  // Calculate and set the height for carousel items (without heading dependency)
  function setCarouselHeight() {
    // Calculate available height without depending on heading element
    const windowHeight = window.innerHeight;
    const carouselContainer = carousel.closest(".container-fluid");

    // Get the carousel container&singleQuote;s offset from top
    const containerRect = carouselContainer
      ? carouselContainer.getBoundingClientRect()
      : { top: 0 };
    const availableHeight = windowHeight - containerRect.top - 100; // 100px for padding/margins

    // Set a minimum height to ensure carousel is always visible
    const carouselHeight = Math.max(availableHeight, 300);

    document.documentElement.style.setProperty(
      "--carousel-height",
      `${carouselHeight}px`
    );
  }

  // Initial setup
  updateConfig();
  initializeClones();
  setCarouselHeight();

  // Start with the first real set of images
  let currentIndex = 0; // Index of current visible center image (0 to totalItems-1)
  let position = itemsPerSlide; // Real position considering clones
  let isAnimating = false;

  // Update carousel position
  function updateCarouselPosition(animate = true) {
    if (animate) {
      carouselInner.style.transition = "transform 0.5s ease";
    } else {
      carouselInner.style.transition = "none";
    }

    const translateX = (position * -100) / itemsPerSlide;
    carouselInner.style.transform = `translateX(${translateX}%)`;
  }

  // Initialize position
  updateCarouselPosition(false);

  // Handle transition end
  carouselInner.addEventListener("transitionend", function () {
    isAnimating = false;

    // Handle infinite loop logic
    if (position >= totalItems + itemsPerSlide) {
      position = itemsPerSlide + (position - (totalItems + itemsPerSlide));
      updateCarouselPosition(false);
    } else if (position < itemsPerSlide) {
      position = totalItems + position;
      updateCarouselPosition(false);
    }

    currentIndex = (position - itemsPerSlide) % totalItems;
  });

  // Navigation functions
  function next() {
    if (isAnimating) return;
    isAnimating = true;
    position += slideBy;
    updateCarouselPosition();
  }

  function prev() {
    if (isAnimating) return;
    isAnimating = true;
    position -= slideBy;
    updateCarouselPosition();
  }

  // Event listeners for buttons
  nextBtn.addEventListener("click", next);
  prevBtn.addEventListener("click", prev);

  // Mouse drag functionality
  let isDragging = false;
  let startX = 0;
  let startPosition = 0;

  // Prevent image drag
  const carouselImages = document.querySelectorAll("#carouselInner img");
  carouselImages.forEach((img) => {
    img.addEventListener("dragstart", (e) => {
      e.preventDefault();
    });
    img.style.pointerEvents = "none";
  });

  carousel.addEventListener("mousedown", startDrag);
  carousel.addEventListener("touchstart", startDrag, { passive: true });

  carousel.addEventListener("mousemove", drag);
  carousel.addEventListener("touchmove", drag, { passive: true });

  carousel.addEventListener("mouseup", endDrag);
  carousel.addEventListener("touchend", endDrag);
  carousel.addEventListener("mouseleave", endDrag);

  function startDrag(e) {
    if (e.target.tagName === "IMG") {
      e.preventDefault();
    }

    if (isAnimating) return;

    isDragging = true;
    startX = e.type.includes("mouse") ? e.clientX : e.touches[0].clientX;
    startPosition = position;
    carousel.classList.add("dragging");
    carouselInner.style.transition = "none";
    document.body.style.cursor = "grabbing";
    document.body.style.userSelect = "none";
    registerUserActivity();
  }

  function drag(e) {
    if (!isDragging) return;

    const x = e.type.includes("mouse") ? e.clientX : e.touches[0].clientX;
    const walk = ((x - startX) / carousel.offsetWidth) * itemsPerSlide;
    const newPosition = startPosition - walk;
    const translateX = (newPosition * -100) / itemsPerSlide;
    carouselInner.style.transform = `translateX(${translateX}%)`;
  }

  function endDrag(e) {
    if (!isDragging) return;

    isDragging = false;
    carousel.classList.remove("dragging");
    document.body.style.cursor = "";
    document.body.style.userSelect = "";
    carouselInner.style.transition = "transform 0.5s ease";

    const x = e.type?.includes("mouse")
      ? e.clientX
      : e.changedTouches
      ? e.changedTouches[0].clientX
      : startX;
    const walk = ((x - startX) / carousel.offsetWidth) * itemsPerSlide;

    if (walk > 0.2) {
      prev();
    } else if (walk < -0.2) {
      next();
    } else {
      updateCarouselPosition();
    }

    registerUserActivity();
  }

  // Keyboard navigation
  document.addEventListener("keydown", function (e) {
    if (
      carousel.offsetParent === null ||
      document.activeElement.tagName === "INPUT" ||
      document.activeElement.tagName === "TEXTAREA" ||
      document.activeElement.isContentEditable
    ) {
      return;
    }

    switch (e.key) {
      case "ArrowLeft":
        e.preventDefault();
        prev();
        registerUserActivity();
        break;
      case "ArrowRight":
        e.preventDefault();
        next();
        registerUserActivity();
        break;
    }
  });

  // Auto-advance system
  let autoAdvanceInterval;
  let userActivityTimeout;

  function startAutoAdvance() {
    clearInterval(autoAdvanceInterval);
    autoAdvanceInterval = setInterval(next, 5000);
  }

  function resetAutoAdvanceTimer() {
    clearTimeout(userActivityTimeout);
    clearInterval(autoAdvanceInterval);
    userActivityTimeout = setTimeout(startAutoAdvance, 10000);
  }

  function registerUserActivity() {
    resetAutoAdvanceTimer();
  }

  startAutoAdvance();

  carousel.addEventListener("mouseenter", () => {
    clearInterval(autoAdvanceInterval);
  });

  carousel.addEventListener("mouseleave", () => {
    resetAutoAdvanceTimer();
  });

  carousel.addEventListener("click", registerUserActivity);
  carousel.addEventListener("wheel", registerUserActivity);

  // Handle window resize
  window.addEventListener("resize", function () {
    const wasMobile = itemsPerSlide === 1;
    updateConfig();
    setCarouselHeight();

    // Only reinitialize if mobile state changed
    if (
      (wasMobile && itemsPerSlide > 1) ||
      (!wasMobile && itemsPerSlide === 1)
    ) {
      initializeClones();
      position = itemsPerSlide; // Reset position
      updateCarouselPosition(false);
    }
  });
});

That’s all! hopefully, you have successfully created carousel with multiple items per slide. If you have any questions or suggestions, feel free to comment below.

Loading... ...

Loading preview...

Device: Desktop
Dimensions: 1200x800
Lines: 0 Characters: 0 Ln 1, Ch 1

Leave a Comment

About W3Frontend

W3Frontend provides free, open-source web design code and scripts to help developers and designers build faster. Every snippet is reviewed before publishing for quality. Learn more.