Skip to content
About Blog Learn Explore Patterns Case studies
  • Home
  • All patterns
  • Carousels

Autoplay carousel

Aug 17, 2021

This carousel uses CSS scroll snap to create smooth, performant slide transitions that do not cause layout shifts.

This carousel can be navigated in a variety of ways: in addition to navigation controls, it supports keyboard navigation and swiping. To maximize usability and readability, the carousel stops auto-transitioning once the user mouseovers within the carousel area.

<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>
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();
#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: #000000;
}
.slide-banner {
    background-color: #000000;
    color: #ffffff;
    position: absolute;
    left: 0;
    bottom: 20px;
    padding: 15px;
    font-size: 2.5vw;
}
.slide-banner a {
    color: #ffffff;
}
#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: #ffffff;
    height: 20px;
    width: 20px;
    background-color: #000000;
    position: absolute;
    padding: 10px;
    opacity: .3;
    cursor: pointer;
}
.arrow.back {
    left: 10px;
    top: 10px;
}
.arrow.forward {
    right: 10px;
    top: 10px;
}
.arrow:hover {
    opacity: 1;
}
Open demo
Last updated: Aug 17, 2021 — Improve article
Share
subscribe

Contribute

  • File a bug
  • View source

Related content

  • developer.chrome.com
  • Chrome updates
  • Case studies
  • Podcasts
  • Shows

Connect

  • Twitter
  • YouTube
  • Google Developers
  • Chrome
  • Firebase
  • Google Cloud Platform
  • All products
  • Terms & Privacy
  • Community Guidelines

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies.