DEV Community

Cover image for Creating A Carousel with Vanilla JS
Felix Owino
Felix Owino

Posted on

Creating A Carousel with Vanilla JS

In the real world, a carousel refers to a series of structures mounted on a circular platform that rotates around a central axis. Examples of carousels in the real world are things like merry-go-round and carousel rides. In the context of user interfaces, carousels are a series of items such as images, videos, files, or texts designed to be viewed one item at a time within a specific container on the screen in a linear sequence. Users can navigate through the items in the carousel by clicking its navigation buttons. Carousels can also be automated to self-navigate at specified intervals. In this article, we will learn how to create a carousel on a webpage using HTML, CSS, and vanilla JavaScript.

Carousels are interesting to view on user interfaces but implementing them is usually a little elusive to beginners. However, it's surprisingly simple to implement once you make sense of what parts of the carousel actually move.

In most cases, for example, an image carousel, we tend to imagine that the carousel is about the motion of the individual images. That assumption looks true from the viewer's point of view but very inaccurate from the engineering point of view. The truth is, that the motion is never about the individual images but the parent container of the images.

All the images in a carousel are children of one div container spread in a horizontal manner. The whole container moves with all its children but only one image is displayed within the viewing container. The other images remain hidden on either side of the viewing container. The viewing container? Yes, you heard me right. Does that mean we have another container apart from the one containing the images? Yes, this container gets to decide how big the displayed image will be and hides everything that does not fit within its borders.

Enough with the words, let us get practical. We will cover this activity in the following sections:

  1. Structuring the carousel
  2. Styling the Carousel
  3. Getting the Carousel in Motion
    • 3.1 Viewing the next image
    • 3.2 Viewing the previous image
    • 3.3 How the Translation works
    • 3.4 Automating the rotation
  4. Adding Indicators

We will start writing the HTML structure of the carousel.

1. Structuring the Carousel

We will use image files for this carousel. I downloaded my images. You can find sample images from Unsplash and download or copy their links for your practice. Below is the HTML code:

 <div class="viewing-container">
        <div class="carousel">
            <img src="./beautiful-home-1.avif" alt="beautiful home 1">
            <img src="/beautiful-home-2.avif" alt="beautiful home 2">
            <img src="./beautiful-home-3.avif" alt="beautiful home 3">
            <img src="./beautiful-home-5.avif" alt="beautiful home 4">
        </div>
        <button class="prev">
            <i class="fa-solid fa-angle-left"></i>
        </button>
        <button class="next">
            <i class="fa-solid fa-angle-right"></i>
        </button>
    </div>
Enter fullscreen mode Exit fullscreen mode

The code snippet above renders the following components:

  • A viewing container that parents the carousel and the navigation buttons
  • The carousel container which contains four image files
  • Four image elements that render four image files
  • A previous navigation button with an icon (left angular brace)
  • A next navigation button with an icon (right angular brace)

We are using Font awesome icons for the angular braces. Font Awesome icons require an external stylesheet to work. Go to cdnjs font awesome library and copy the link tag to the stylesheet (marked with this icon - </>). You will find a long list of links, the first link on the list should work. Paste the link within the <head> of your HTML file. The head of your HTML file should resemble the one below. The Font Awesome link doesn't have to be an exact match because it is subject to change.

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="./style.css">

    <!-- Font Awesome Stylesheet -->
    <link rel="stylesheet" 
        href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" 
        integrity="sha512-Kc323vGBEqzTmouAECnVceyQqyqdsSiqLQISBL29aUW4U/M7pSPA/gEUZQqv1cwx4OnYxTxve5UMg5GT6L4JJg==" 
        crossorigin="anonymous" referrerpolicy="no-referrer" />

    <title>Carousel</title>
</head>
Enter fullscreen mode Exit fullscreen mode

That is all the HTML we need for now, let us add some basic styles to the carousel.

2. Styling the Carousel

The following code snippet contains a few basic styles for the carousel:

body{
    padding-block-start: 4rem;
}

.carousel{
    display: flex;
}

.viewing-container{
    width: 25%;
    position: relative;
    border: solid 3px red;
}

.carousel img{
    width: 100%;
}

button{
    position: absolute;
    top: 50%;
    padding: .4rem .8rem;
    border:  none;
    background-color: rgba(0, 0, 0, 0.5);
    color: white;
    font-size: 1.5rem;
}

button.next{
    right: 1rem;
}

button.prev{
    left: 1rem;
} 
Enter fullscreen mode Exit fullscreen mode

See the result of the styles in the screenshot below then let me explain just what we have done.

carousel images

Using the CSS styles above, we have done the following:

  • We have horizontally spread the images in the carousel container using flex
  • We have intentionally set the width of the viewing container to 25% to allow us to see all the images within the screen. If you have more than 4 images, 25% width won't have the same effect.
  • We have set the position property of the viewing container to relative to allow us to position the navigation buttons on it.
  • We have intentionally added a red solid border around the viewing container to distinguish it from the rest of the screen.
  • We have set every image to be 100% as wide as the viewing container.
  • We have set the positions of the buttons as absolute, vertically centered, and 1 rem from the edges of the viewing container.

We have intentionally left the styling unfinished (with overflow) so that you can see that creating a carousel begins by spreading all the images horizontally. We would also choose to do it vertically but we want to create a horizontal carousel. Before we finish the styling, we will get the carousel in motion using JavaScript.

3. Getting the Carousel in Motion

There are two ways to make a carousel go around. You can use the navigation buttons or make the carousel rotate automatically in specified intervals. We will implement both approaches starting with the navigation buttons.

There are two navigation buttons on the carousel viewing container: a previous button and a next button. If we want to see the images to the right, we will click the next button (the button to the right of the viewing container). Likewise, to view the images to the left of the viewing container, we click the previous button (the button to the left of the container).

Let's explore how we can program the carousel to enable viewing images on the right side of the container.

3.1 Viewing the next Image.

As you click the next button, you expect the image on the right side of the viewing container to be moved inside the viewing container. Let's implement that in the code snippet below:

const carousel = document.querySelector('.carousel');
const nextButton = document.querySelector('.next');

const carouselLength = document.querySelectorAll(
       '.carousel img').length;

let currentPositon = 0

nextButton.addEventListener('click', () => {
    currentPositon = currentPositon < carouselLength - 1 ? currentPositon + 1 : 0

    const offset = -100 * currentPositon
    carousel.style.transform = `translateX(${offset}%)`
})
Enter fullscreen mode Exit fullscreen mode

Confused? Don't worry, you will understand what's going on in no time.

  • First, we get the carousel container, the div element that parents all the images.
  • Secondly, we get the next button, which is the button to the right of the viewing container.
  • Then we get the length of the carousel. This is equal to the number of images in the carousel. We get the length of the NodeList of all images in the carousel. The NodeList and all its contents are totally useless to us except its length.
  • We initialize our starting position to 0. This is where we are before the carousel begins to move. The current position represents the index (in NodeList) of the image displayed within the viewing container.

That's all for the variables, let's talk about the click event handler.

Here, we adjust the value of currentPosition by 1 to point to the next image in the carousel.

currentPositon = currentPositon < carouselLength - 1 ? currentPositon + 1 : 0
Enter fullscreen mode Exit fullscreen mode

Take note that we do this conditionally, we only increment the position up to the time when it points to the last image in the carousel, and then we go back to 0. If we still want to view the next image after the last, we are returned back to where we started. Without the conditional checks, we will continue to view empty spaces.

The interesting part is saved for last,

    const offset = -100 * currentPositon
    carousel.style.transform = `translateX(${offset}%)`
Enter fullscreen mode Exit fullscreen mode

we translate the carousel leftwards in relation to the value of the currentPosition. We negate the offset (translation distance) because we want the carousel to move leftwards. We multiply the currentPosition by 100 to calculate the offset as a percentage-based translation, ensuring that each item in the carousel is translated by its full width. Think about it as swiping left to see what's on the right.

Save your work and try clicking the next button to see for yourself how the carousel container moves to show you the next image.

You must have realized how rough and sudden the motion of the carousel is. It's not as gradual and smooth as you expect. Let's fix that by customizing the transition of the carousel using CSS.

.carousel{
    display: flex;
    transition-property: all;
    transition-duration: 2s;
    transition-delay: 0;
    transition-timing-function: ease;
}
Enter fullscreen mode Exit fullscreen mode

Update the styles in the carousel class with the above properties and values and see how smooth and gradual the motion gets.

We are done with the next button. Let us make the previous button work just as well if not better.

3.2 Viewing the previous Image.

Calculating the position for the previous image takes the direct opposite of the calculation we performed when viewing the next image. We decrement the value of the current position until the value becomes 0. Once it reaches 0, instead of decrementing to -1, the currentPosition will be reinitialized to the last position in the carousel. If the value of currentPosition is zero, pressing the previous button will take you to the last image in the carousel, the end. The following code snippet extends the previous scripts:

const prevButton = document.querySelector('.prev');

prevButton.addEventListener('click', () =>{
    currentPositon = currentPositon > 0 ? currentPositon - 1 : carouselLength - 1

    const offset = -100 * currentPositon
    carousel.style.transform = `translateX(${offset}%)`
})
Enter fullscreen mode Exit fullscreen mode

This is similar to swiping right to see what's on the left.

Have you noticed that we are still negating the offset even though we want the carousel to move rightwards? Why aren't we making it a positive translation here? Why is the calculation the same as what we did in the previous section but the effect looks like the opposite? You will understand what's happening shortly, but first, let's make the JavaScript code a little cleaner.

const carousel = document.querySelector('.carousel');
const prevButton = document.querySelector('.prev');
const nextButton = document.querySelector('.next');

const carouselLength = document.querySelectorAll('.carousel img').length;

let currentPositon = 0

const showImage = (currentPositon) =>{
    const offset = -100 * currentPositon
    carousel.style.transform = `translateX(${offset}%)`
}

nextButton.addEventListener('click', () => {
    currentPositon = currentPositon < carouselLength - 1 ? currentPositon + 1 : 0
    showImage(currentPositon)
})

prevButton.addEventListener('click', () =>{
    currentPositon = currentPositon > 0 ? currentPositon - 1 : carouselLength - 1
    showImage(currentPositon)
})
Enter fullscreen mode Exit fullscreen mode

The code snippet above achieves the same goal with fewer lines of code in the click event handlers. We have refactored the code by encapsulating the translation and offset calculation in a reusable function, showImage.

Now let's learn why we are always doing a negative translation when it looks like we are changing directions.

3.3 How The Translation Works

In practice, the translation distance and the translation direction of an element are always calculated relative to its origin, the initial position of the element without any translation.

If the intended translation destination of an element lies to the right of the element before any translation, its translation direction will always be positive regardless of what it looks like in the display. Likewise, the translation direction of an element will always be negative if the translation destination lies to the left of its translation origin.

In our case, all the images in the carousel before translation are always on the right-hand side of the viewing container. It does not matter whether, from the look of things the images are to the left or right side of the viewing container, the computer will always reset to the original positions (right-hand side of the viewing container) before executing a translation.

Assume you want to see the image at position 4 in the carousel, the translation direction and translation distance will always be negative and 400% (-400%) respectively. The offset will always be -400% whether you are clicking the previous button or the next button.

I hope that helped clear your doubts if you had any. Now let's wind up by centering the viewing container, increasing its width, removing the border, and hiding the images that lie outside the viewing container using the following CSS rules:

.viewing-container{ 
    /*Remove the border*/
    position: relative;
    width: 50%; /* Increase from 25 to 50*/
    overflow: hidden; /*Hide overflow*/
    margin: auto; /*Center the container*/
}
Enter fullscreen mode Exit fullscreen mode

The screenshot below shows the centered, enlarged, and borderless viewing container without overflows:

Centered Carousel

3.4 Automating the rotation.

Remember we did say earlier that the motion of the carousel can be automated. We can use the setInterval function to calculate the currentPosition and translate the carousel after specific intervals. The code snippet below implements the automation of a carousel to rotate after every 5 seconds.

setInterval(() =>{
    currentPositon = currentPositon < carouselLength -1 ? currentPositon + 1 : 0
    showImage(currentPositon)
}, 5000)
Enter fullscreen mode Exit fullscreen mode

There is one problem you will experience by including this automation. There seems to be an interruption when you want to control the motion with the navigation buttons. To fix that problem, you can store the value returned by the setInterval function and use it to clear the intervals once a button is clicked. The code snippet below shows the complete code snippet with the fixes.

// // script.js
const prevButton = document.querySelector('.prev');
const carousel = document.querySelector('.carousel');
const carouselLength = document.querySelectorAll('.carousel img').length;
const nextButton = document.querySelector('.next');

let currentPositon = 0

const showImage = (currentPositon) =>{
    const offset = -100 * currentPositon
    carousel.style.transform = `translateX(${offset}%)`
}

const interval = setInterval(() =>{
    currentPositon = currentPositon < carouselLength -1 ? currentPositon + 1 : 0

    showImage(currentPositon)
}, 5000)

nextButton.addEventListener('click', () => {
    clearInterval(interval)

    currentPositon = currentPositon < carouselLength - 1 ? currentPositon + 1 : 0

    showImage(currentPositon)
})

prevButton.addEventListener('click', () =>{
    clearInterval(interval)

    currentPositon = currentPositon > 0 ? currentPositon - 1 : carouselLength - 1

    showImage(currentPositon)
})
Enter fullscreen mode Exit fullscreen mode

With the latest adjustments, the carousel will autorotate only until you click either of the navigation buttons. Once the manual navigation takes over, the automated navigation will stop until the page reloads.

4 Adding Indicators

Indicators in a carousel help users know the number of images they have viewed and the ones they haven't viewed. We will create four circular dots at the bottom of the viewing container to act as indicators. We can create circles by adding 4 div elements to the markup. We can use CSS to set their height, width, border, and border radius to make them circular.

We will extend the viewing container with more elements as in the code snippet below.

    <div class="viewing-container">
//Initial content - carousel container and buttons
// go here
        <div class="indicators">
            <div class="indicator"></div>
            <div class="indicator"></div>
            <div class="indicator"></div>
            <div class="indicator"></div>
        </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

The following CSS styles will make the div elements look just like we want.

.indicators{
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .8rem;
    position: absolute;
    bottom: 1rem;
    width: 100%;
}

.indicator{
    width: 8px;
    height: 8px;
    border: solid 1px white;
    border-radius: 50%;
    transition: background-color 2s ease;
}
Enter fullscreen mode Exit fullscreen mode

After adding the markup and applying the styles, you should have a series of indicators that resemble the ones shown in the screenshot below.

Carousel indicators

Notice the four circular dots at the bottom of the screen.

Having successfully rendered and styled indicators, let us now make them do what they are supposed to do. We will get all the indicators from the DOM and create a function that changes the background color of the dots as the carousel moves. The following code snippet implements the actions described.

const indicators = document.querySelectorAll('.indicator')

const moveIndicator = (currentPositon) =>{
    indicators.forEach((indicator, index) =>{
        indicator.style.backgroundColor = currentPositon === index ? 
            'white': 'rgba(0,0,0,0.5)'
    })
}
Enter fullscreen mode Exit fullscreen mode

In the code snippet above, we get all the indicators in a NodeList and create a function called moveIndicator. The function accepts the currentPosition as an argument, and iterates over the NodeList comparing the currentPosition with the index value of every indicator in the list. If the currentPosition equals the index, we set the background color to white, otherwise, we set it to a semi-transparent black color.

We have to call the function every time the carousel moves to update the indicator. Below is the final code snippet with the indicator working as expected.

// // script.js
const prevButton = document.querySelector('.prev');
const carousel = document.querySelector('.carousel');
const carouselLength = document.querySelectorAll('.carousel img').length;
const nextButton = document.querySelector('.next');
const indicators = document.querySelectorAll('.indicator')

let currentPositon = 0

const showImage = (currentPositon) =>{
    const offset = -100 * currentPositon
    carousel.style.transform = `translateX(${offset}%)`
}

const moveIndicator = (currentPositon) =>{
    indicators.forEach((indicator, index) =>{
        indicator.style.backgroundColor = currentPositon === index ?
            'white': 'rgba(0,0,0,0.5)'
    })
}

const interval = setInterval(() =>{
    currentPositon = currentPositon < carouselLength -1 ? 
        currentPositon + 1 : 0

    showImage(currentPositon)
    moveIndicator(currentPositon)
}, 5000)

nextButton.addEventListener('click', () => {
    clearInterval(interval)

    currentPositon = currentPositon < carouselLength - 1 ?
         currentPositon + 1 : 0

    showImage(currentPositon)
    moveIndicator(currentPositon)
})

prevButton.addEventListener('click', () =>{
    clearInterval(interval)

    currentPositon = currentPositon > 0 ? 
        currentPositon - 1 : carouselLength - 1

    showImage(currentPositon)
    moveIndicator(currentPositon)
})
Enter fullscreen mode Exit fullscreen mode

The screenshot below indicates that we are currently viewing the second image in the carousel.

Indicating second image

Finally, we are done creating a carousel with vanilla JavaScript (without third-party libraries). Carousels come in handy when you want to display a series of images, videos, documents, or texts within a limited space. Creating a carousel involves horizontally translating the parent container of the images or files that make up the carousel. The translation direction of a carousel will always be the same regardless of the position of the previous translations. The translation distance and translation direction for displaying the same image will always be the same regardless of the direction of navigation. Carousels can be navigated manually or automatically with set intervals.

Congratulations, now try creating your carousel from what you have learned. Feel free to say something in the comment section. Find out more about CSS translations from MDN.

Top comments (0)