インジケーターを追加したカルーセルのサンプル

<div class="carousel-outer">
  <div class="carousel">
    <div class="carousel__section">
      <img src="https://placehold.jp/ccffff/000000/960x540.png?text=Section1">
    </div>
    <div class="carousel__section">
      <img src="https://placehold.jp/ffccff/000000/960x540.png?text=Section2">
    </div>
    <div class="carousel__section">
      <img src="https://placehold.jp/ccffcc/000000/960x540.png?text=Section3">
    </div>
  </div>
  <button class="carousel__button-left">←</button>
  <button class="carousel__button-right">→</button>

  <ul class="carousel__indicator">
    <li class="active"></li>
    <li></li>
    <li></li>
  </ul>
</div>
img {
  max-width: 100%;
  height: auto;
}

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

.carousel-outer {
  position: relative;
  overflow: hidden;
  width: 100%;
  max-width: 960px;
}

.carousel {
  display: flex;
  transform: translateX(-100%);
  transition: transform 0.5s ease-in-out;
}

.no-transition {
  transition: none;
}

.carousel__section {
  min-width: 100%;
}

.carousel__button-left,
.carousel__button-right {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  cursor: pointer;
}

.carousel__button-left {
  left: 5%;
}

.carousel__button-right {
  right: 5%;
}

.carousel__indicator {
  display: flex;
  position: absolute;
  bottom: 5%;
  left: 50%;
  transform: translateX(-50%);
}

.carousel__indicator > li {
  width: 10px;
  height: 10px;
  margin: 0 5px;
  border-radius: 50%;
  border: 1px solid #000;
}

.carousel__indicator > .active {
  background: #000
}
let current = 1
let isAnimation = false;
const carousel = document.querySelector('.carousel')
const carousels = document.querySelectorAll('.carousel__section')
const carouselLen = carousels.length
const lastCarousel = carouselLen + 1
const total = carouselLen + 2;
const newOrder = [];

newOrder.push(carousels[carouselLen - 1].outerHTML)
carousels.forEach((section) => {
  newOrder.push(section.outerHTML)
})
newOrder.push(carousels[0].outerHTML)
carousel.innerHTML = newOrder.join('')

function updateIndicators(newIndex) {
  if (newIndex < 0) {
    newIndex = carouselLen - 1
  } else if (newIndex > (carouselLen - 1)) {
    newIndex = 0
  }
  document.querySelectorAll('.carousel__indicator > li').forEach((dot, index) => {
    dot.classList.toggle('active', index === newIndex)
  })
}

function showSection() {
  isAnimation = true;
  carousel.style.transform = `translateX(${-current * 100}%)`;
  updateIndicators(current - 1)
}

function prevSection() {
  if (isAnimation) return

  current = current === 0 ? carouselLen + 1 : current - 1;
  showSection()
}

function nextSection() {
  if (isAnimation) return

  current = (current + 1) % (carouselLen + 2);
  showSection()
}

const btnLeft = document.querySelector('.carousel__button-left')
const btnRight = document.querySelector('.carousel__button-right')

btnLeft.addEventListener('click', prevSection)
btnRight.addEventListener('click', nextSection)

carousel.addEventListener('transitionend', () => {
  if (current === carouselLen + 1) {
    current = 1
  } else if (current === 0) {
    current = carouselLen
  }

  carousel.classList.add('no-transition')
  carousel.style.transform = `translateX(${-current * 100}%)`
  setTimeout(() => {
    carousel.classList.remove('no-transition')
    isAnimation = false
  }, 20)
})

function handleKeyDown(event) {
  if (isAnimation) return

  if (event.key === 'ArrowLeft') {
    prevSection()
  } else if (event.key === 'ArrowRight') {
    nextSection()
  }
}

document.addEventListener('keydown', handleKeyDown)

元記事を表示する