自動再生カルーセル

このカルーセルは CSS スクロール スナップを使用して、レイアウト シフトの原因とならない、スムーズでパフォーマンスの高いスライド遷移を作成します。

このカルーセルでは、ナビゲーション コントロールに加えて、キーボード ナビゲーションとスワイプなど、さまざまな方法で移動できます。ユーザビリティと読みやすさを最大限に高めるため、ユーザーがカルーセル領域内でマウスオーバーを行うと、カルーセルは自動移行を停止します。

<div id="carousel">
 
<div id="slide-container">
   
<div class="slide" data-slideIndex="0">
     
<div class="slide-banner">Tour the Empire State Building! <a href="">Buy tickets now.</a></div>
     
<img width="1200" height="600" src="./newyork.jpg">
   
</div>
   
<div class="slide" data-slideIndex="1">
     
<div class="slide-banner">Ride the Shinkansen! <a href="">Buy tickets now.</a></div>
     
<img width="1200" height="600" src="./tokyo.jpg">
   
</div>
   
<div class="slide" data-slideIndex="2">
     
<div class="slide-banner">Discover relaxation! <a href="">Buy tickets now.</a></div>
     
<img width="1200" height="600" src="./beach.jpg">
   
</div>
   
<div class="slide" data-slideIndex="3">
     
<div class="slide-banner">See penguins! <a href="">Buy tickets now.</a></div>
     
<img width="1200" height="600" src="./penguins.jpg">
   
</div>
   
<div class="slide" data-slideIndex="4">
     
<div class="slide-banner">Take a ride on the wheel! <a href="">Buy tickets now.</a></div>
     
<img width="1200" height="600" src="./wheel.jpg">
   
</div>
 
</div>
 
<div id="back-button" class="arrow back"></div>
 
<div id="forward-button" class="arrow forward"></div>
 
<div class="slide-indicators">
   
<div class="slide-indicator active"></div>
   
<div class="slide-indicator"></div>
   
<div class="slide-indicator"></div>
   
<div class="slide-indicator"></div>
   
<div class="slide-indicator"></div>
 
</div>
</div>

       
#carousel {
   
max-width: 1200px;
   
display: flex;
   
flex-direction: column;
   
margin: 0 auto;
   
position: relative;
}  
.slide-indicators {
   
display: flex;
   
justify-content: center;
}
.slide-indicator {
   
height: 44px;
   
width: 50px;
   
display: flex;
   
justify-items: center;
   
cursor: pointer;
}
.slide-indicator:after {
   
content: "";
   
background-color: #878787;
   
height: 10px;
   
margin-top: 10px;
   
width: 40px;
}
.slide-indicator.active:after,
.slide-indicator:hover:after {
   
background-color: #000;
}
.slide-banner {
   
background-color: #000;
   
color: #fff;
   
position: absolute;
   
left: 0;
   
bottom: 20px;
   
padding: 15px;
   
font-size: 2.5vw;
}
.slide-banner a {
   
color: #fff;
}
#slide-container {
   
scroll-snap-type: x mandatory;
   
overflow-x: scroll;
   
overflow-y: hidden;
   
display: flex;
   
align-items: center;
   
height: 100%;
   
gap: 10px;
   
-webkit-overflow-scrolling: touch;
   
scroll-behavior: smooth;
}
.slide {
   
scroll-snap-align: center;
   
position: relative;
   
min-width: 100%;
   
padding-top: 50%;
}
.slide img {
   
height: 100%;
   
width: auto;
   
position: absolute;
   
top: 0;
   
left: 0;
}
.arrow {
   
color: #fff;
   
height: 20px;
   
width: 20px;
   
background-color: #000;
   
position: absolute;
   
padding: 10px;
   
opacity: .3;
   
cursor: pointer;
}
.arrow.back {
   
left: 10px;
   
top: 10px;
}
.arrow.forward {
   
right: 10px;
   
top: 10px;
}
.arrow:hover {
   
opacity: 1;
}
       

       
function autoplayCarousel() {
   
const carouselEl = document.getElementById("carousel");
   
const slideContainerEl = carouselEl.querySelector("#slide-container");
   
const slideEl = carouselEl.querySelector(".slide");
    let slideWidth
= slideEl.offsetWidth;
   
// Add click handlers
    document
.querySelector("#back-button")
       
.addEventListener("click", () => navigate("backward"));
    document
.querySelector("#forward-button")
       
.addEventListener("click", () => navigate("forward"));
    document
.querySelectorAll(".slide-indicator")
       
.forEach((dot, index) => {
            dot
.addEventListener("click", () => navigate(index));
            dot
.addEventListener("mouseenter", () => clearInterval(autoplay));
       
});
   
// Add keyboard handlers
    document
.addEventListener('keydown', (e) => {
       
if (e.code === 'ArrowLeft') {
            clearInterval
(autoplay);
            navigate
("backward");
       
} else if (e.code === 'ArrowRight') {
            clearInterval
(autoplay);
            navigate
("forward");
       
}
   
});
   
// Add resize handler
    window
.addEventListener('resize', () => {
        slideWidth
= slideEl.offsetWidth;
   
});
   
// Autoplay
   
const autoplay = setInterval(() => navigate("forward"), 3000);
    slideContainerEl
.addEventListener("mouseenter", () => clearInterval(autoplay));
   
// Slide transition
   
const getNewScrollPosition = (arg) => {
       
const gap = 10;
       
const maxScrollLeft = slideContainerEl.scrollWidth - slideWidth;
       
if (arg === "forward") {
           
const x = slideContainerEl.scrollLeft + slideWidth + gap;
           
return x <= maxScrollLeft ? x : 0;
       
} else if (arg === "backward") {
           
const x = slideContainerEl.scrollLeft - slideWidth - gap;
           
return x >= 0 ? x : maxScrollLeft;
       
} else if (typeof arg === "number") {
           
const x = arg * (slideWidth + gap);
           
return x;
       
}
   
}
   
const navigate = (arg) => {
        slideContainerEl
.scrollLeft = getNewScrollPosition(arg);
   
}
   
// Slide indicators
   
const slideObserver = new IntersectionObserver((entries, observer) => {
        entries
.forEach(entry => {
           
if (entry.isIntersecting) {
               
const slideIndex = entry.target.dataset.slideindex;
                carouselEl
.querySelector('.slide-indicator.active').classList.remove('active');
                carouselEl
.querySelectorAll('.slide-indicator')[slideIndex].classList.add('active');
           
}
       
});
   
}, { root: slideContainerEl, threshold: .1 });
    document
.querySelectorAll('.slide').forEach((slide) => {
        slideObserver
.observe(slide);
   
});
}
autoplayCarousel
();