3D Stacked Carousel with JavaScript and Tailwind CSS

Category: Javascript , Slider | May 16, 2025
Author:iamajraj
Views Total:101 views
Official Page:Go to website
Last Update:May 16, 2025
License:MIT

Preview:

3D Stacked Carousel with JavaScript and Tailwind CSS

Description:

This is a Vanilla JavaScript slider that transforms a series of HTML elements into an interactive, 3D-stacked carousel.

It manages the state of each slide (active, next, previous, etc.) with JavaScript and then uses CSS transforms and opacity to create the stacking and transition effects.

More Features:

  • Tailwind CSS Styled: Uses Tailwind utility classes for styling and responsiveness.
  • Responsive Design: Works on different screen sizes via Tailwind’s responsive prefixes.
  • Dynamic Shadow Effects: Applies different shadow intensities based on a slide’s position in the stack.
  • Handles Varying Slide Counts: Adapts its display logic for different numbers of slides.

How to use it:

1. Make sure you first have Tailwind CSS loaded in the document.

<script src="https://cdn.tailwindcss.com"></script>

2. Lay out your carousel items. Each slide (article) with the class slide will be an individual card.

<div class="relative w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-lg xl:max-w-xl mx-auto">
  <-- Slides -->
  <main id="slider-container" class="relative w-full h-[400px] sm:h-[380px] md:h-[360px] [perspective:1200px]">
    <article class="slide absolute inset-0 flex flex-col justify-between p-6 md:p-8 bg-white rounded-2xl border border-stone-200">
      <div>
        <blockquote class="text-sm md:text-base text-stone-600 mb-4 md:mb-6 leading-relaxed">
          Slide 1
        </blockquote>
      </div>
      <div class="flex items-center mt-6 pt-4 border-t border-stone-200">
        <img src="avatar.svg" alt="User Name" class="w-10 h-10 rounded-full mr-3 shrink-0">
        <div>
          <p class="font-semibold text-stone-800 text-sm">User Name</p>
          <p class="text-xs text-stone-500">Marketing Manager</p>
        </div>
      </div>
    </article>
    ... more articles (slides) here ...
  </main>
  <!-- Controls -->
  <button id="prevButton" title="Previous testimonial" class="absolute top-1/2 left-[-16px] sm:left-[-24px] md:left-[-30px] transform -translate-y-1/2 z-20 w-8 h-8 sm:w-10 sm:h-10 rounded-full bg-white/80 backdrop-blur-sm shadow-md hover:bg-white focus:bg-white flex items-center justify-center text-stone-500 hover:text-stone-700 focus:text-stone-700 focus:outline-none transition-all duration-200">
    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" class="w-4 h-4 sm:w-5 sm:h-5">
      <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
    </svg>
  </button>
  <button id="nextButton" title="Next testimonial" class="absolute top-1/2 right-[-16px] sm:right-[-24px] md:right-[-30px] transform -translate-y-1/2 z-20 w-8 h-8 sm:w-10 sm:h-10 rounded-full bg-white/80 backdrop-blur-sm shadow-md hover:bg-white focus:bg-white flex items-center justify-center text-stone-500 hover:text-stone-700 focus:text-stone-700 focus:outline-none transition-all duration-200">
    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor" class="w-4 h-4 sm:w-5 sm:h-5">
      <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
    </svg>
  </button>
</div>

3. Define the appearance and transition for each slide state.

.slide {
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  transition: all 0.6s cubic-bezier(0.65, 0, 0.35, 1);
  will-change: transform, opacity;
}
.slide[data-status="active"] {
  opacity: 1;
  transform: translateY(0%) translateX(0%) translateZ(0px) scale(1);
  z-index: 10;
}
.slide[data-status="next"] {
  opacity: 0.85;
  transform: translateY(4%) translateX(18%) translateZ(-50px) scale(0.92);
  z-index: 9;
}
.slide[data-status="prev"] {
  opacity: 0.85;
  transform: translateY(4%) translateX(-18%) translateZ(-50px) scale(0.92);
  z-index: 9;
}
.slide[data-status="background-next"] {
  opacity: 0.6;
  transform: translateY(8%) translateX(30%) translateZ(-100px) scale(0.84);
  z-index: 8;
}
.slide[data-status="background-prev"] {
  opacity: 0.6;
  transform: translateY(8%) translateX(-30%) translateZ(-100px) scale(0.84);
  z-index: 8;
}
.slide[data-status="hidden"] {
  opacity: 0;
  transform: translateY(12%) translateX(0%) translateZ(-150px) scale(0.76);
  z-index: 1;
  pointer-events: none;
}

4. Use JavaScript to handle slide state management and navigation:

document.addEventListener('DOMContentLoaded', () => {
  const sliderContainer = document.getElementById('slider-container');
  const slides = Array.from(sliderContainer.querySelectorAll('.slide'));
  const numSlides = slides.length;
  let currentActiveIndex = 0;
  const prevButton = document.getElementById('prevButton');
  const nextButton = document.getElementById('nextButton');
  function updateSlideStates() {
    if (numSlides === 0) return;
    for (let i = 0; i < numSlides; i++) {
      const slide = slides[i];
      let status = 'hidden';
      let diff = i - currentActiveIndex;
      if (diff > numSlides / 2) {
        diff -= numSlides;
      } else if (diff < -numSlides / 2) {
        diff += numSlides;
      }
      if (diff === 0) {
        status = 'active';
      } else if (numSlides > 1 && diff === 1) {
        status = 'next';
      } else if (numSlides > 2 && diff === -1) {
        status = 'prev';
      } else if (numSlides > 3 && diff === 2) {
        status = 'background-next';
      } else if (numSlides > 4 && diff === -2) {
        status = 'background-prev';
      }
      if (numSlides === 2 && diff === -1) {
        status = 'next';
      }
      if (numSlides === 3 && diff === -2) { // For N=3, diff=-2 is same as diff=1 ('next')
        // This case is diff=2, which normalizes to diff=-1 for N=3, so 'prev'
        // The diff calc already handles this.
      }
      if (numSlides === 4 && diff === -2) { // For N=4, diff=-2 is same as diff=2 ('background-next')
        status = 'background-next';
      }
      slide.dataset.status = status;
      slide.classList.remove('shadow-xl', 'shadow-lg', 'shadow-md');
      if (status === 'active') slide.classList.add('shadow-xl');
      else if (status === 'next' || status === 'prev') slide.classList.add('shadow-lg');
      else if (status === 'background-next' || status === 'background-prev') slide.classList.add('shadow-md');
    }
  }
  if (numSlides > 0) {
    updateSlideStates();
  } else {
    if (sliderContainer) sliderContainer.innerHTML = '<p class="text-stone-500 text-center p-10 absolute inset-0 flex items-center justify-center">No testimonials available.</p>';
  }
  if (numSlides <= 1) {
    if (prevButton) prevButton.style.display = 'none';
    if (nextButton) nextButton.style.display = 'none';
  } else {
    if (prevButton) {
      prevButton.addEventListener('click', () => {
        currentActiveIndex = (currentActiveIndex - 1 + numSlides) % numSlides;
        updateSlideStates();
      });
    }
    if (nextButton) {
      nextButton.addEventListener('click', () => {
        currentActiveIndex = (currentActiveIndex + 1) % numSlides;
        updateSlideStates();
      });
    }
  }
});

You Might Be Interested In:


Leave a Reply