How to Build a Responsive Image Slider Using Vanilla JavaScript

An image slider is a great way to display multiple images in a compact space while maintaining a smooth user experience. In this tutorial, we will build a simple yet fully responsive image slider using only HTML, CSS, and JavaScript—no libraries required!

Final Output Preview

Before we dive into the code, here is a preview of what we will build:

Create a Simple Infinite Loop Slider Using Vanilla JavaScript

Step 1: Setting Up the HTML Structure

We'll start by creating a simple HTML file with the basic structure of the slider.

<h1>Slider Using Vanilla Js</h1>
    <div class="sliderContainer">
        <div class="slider">
            <div class="slide"><img src="./images/img-1.webp" alt=""></div>
            <div class="slide"><img src="./images/img-2.webp" alt=""></div>
            <div class="slide"><img src="./images/img-3.webp" alt=""></div>
            <div class="slide"><img src="./images/img-4.webp" alt=""></div>
            <div class="slide"><img src="./images/img-5.webp" alt=""></div>
            <div class="slide"><img src="./images/img-6.webp" alt=""></div>
        </div>
        <!-- arrows -->
        <div class="arrows">
            <button class="prev">❮</button>
            <button class="next">❯</button>
        </div>
        <!-- navigation dots -->
        <div class="dots">
            <!-- we will add these dots using Js -->
            <!-- <span class="dot active"></span>
            <span class="dot"></span>
            <span class="dot"></span>
            <span class="dot"></span> -->
        </div>

Explanation:

  • We create a sliderContainer to hold all the slider,arrows and dots it is work as a wrapper element for all of them.
  • we add a slider div containing multiple slide div which furthur containing an image img elements.
  • We added prev and next buttons to navigate through the images.
  • We added dots which will containing navigation dots

Step 2: Styling the Slider with CSS

Next, let's add CSS to make the slider look visually appealing and responsive.

*,
    *::before,
    *::after {
        padding: 0;
        margin: 0;
        box-sizing: border-box;
    }

    body {
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
        padding: 20px;
        font-family: Arial, Helvetica, sans-serif;
        background: linear-gradient(135deg, #ff9a9e, #fad0c4);
    }

    body>h1 {
        text-transform: capitalize;
        color: white;
        text-shadow: 0px 2px 5px rgba(0, 0, 0, 0.373);
        margin-bottom: 1rem;
        font-size: clamp(2rem, 3vw, 2.5rem);
    }

    .sliderContainer {
        max-width: 600px;
        overflow: hidden;
        border-radius: 15px;
        position: relative;
        isolation: isolate;
    }

    .slider {
        display: flex;
        transition: 300ms ease;
    }

    .slider .slide {
        flex-shrink: 0;
        width: 100%;
    }

    .slider .slide img {
        width: 100%;
        height: 100%;
        object-fit: cover;
    }

    .sliderContainer .arrows {
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        width: 96%;
        left: 2%;
        display: flex;
        justify-content: space-between;
    }

    .sliderContainer .arrows button {
        border: none;
        background-color: rgba(0, 0, 0, 0.495);
        color: white;
        font-size: clamp(1.2rem, 1.5vw, 1.8rem);
        width: 45px;
        height: 45px;
        display: flex;
        justify-content: center;
        align-items: center;
        border-radius: 50%;
        transition: 300ms ease;
        cursor: pointer;
    }

    .sliderContainer .arrows button:hover {
        background-color: rgba(0, 0, 0, 0.736);
        transform: scale(1.1);
    }

    .sliderContainer .arrows button:active {
        background-color: rgba(0, 0, 0, 0.884);
        transform: scale(1);
    }

    .sliderContainer .dots {
        display: flex;
        gap: 0.5rem;
        justify-content: center;
        align-items: center;
        position: absolute;
        bottom: 0.5rem;
        width: 100%;
        /* padding: 1rem;
        background-color: red; */
    }

    .sliderContainer .dots .dot {
        width: 12px;
        height: 12px;
        border-radius: 50%;
        box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.444);
        background-color: rgba(255, 255, 255, 0.495);
        transition: 200ms ease;
        cursor: pointer;
    }

    .sliderContainer .dots .dot.active {
        background-color: white;
        transform: scale(1.3);
    }

    .sliderContainer .dots .dot:hover {
        background-color: white;
    }

Explanation:

  • The sliderContainer holds the elements of slider so we give it styling of position : relative so that all the absolute elements move according to its bouderies
  • The slider uses display: flex to position slides in a row.
  • Each img inside the slide takes up 100% of the container width.
  • Navigation buttons are positioned at the sides with absolute positioning.

Step 3: Adding JavaScript for Functionality

Now, let's implement the JavaScript functionality to make the slider work.

document.addEventListener('DOMContentLoaded', () => {
                initializeSlider();
            })
     function initializeSlider() {
        // Slider setup code goes here
        }

Explanation:

The above code waits until the DOM content is fully loaded before running the initializeSlider function, which will set up the slider.

Part 1: Basic Slider Without Smooth Sliding Effect

1. Select all the HTML elements in JavaScript

const sliderContainer = document.querySelector('.sliderContainer');
    const slider = sliderContainer.querySelector('.slider');
    const slides = [...slider.querySelectorAll('.slide')];
    const nextBtn = sliderContainer.querySelector('.arrows .next')
    const prevBtn = sliderContainer.querySelector('.arrows .prev')
    const dotsContainer = sliderContainer.querySelector('.dots');

Explanation:

Here, we are using document.querySelector() to select all the necessary elements for our slider, such as the container, slides, navigation buttons, and dots.

2. Create Slider Variables

// Slider variables
    let currentIndex = 0;
    let isAnimating = false;
    let interval;

Explanation:

In this block, we define some key variables. currentIndex tracks the currently active slide, isAnimating prevents multiple animations from running simultaneously, and `interval` is for auto-sliding functionality.

3. Insert Dots Dynamically

// Insert dots
    slides.forEach((slide, index) => {
        let dot = document.createElement('span');
        dot.classList.add('dot');
        index == currentIndex && dot.classList.add('active');
        dotsContainer.appendChild(dot);
    });
    // Select all dots in JS
    const allDots = [...dotsContainer.querySelectorAll('.dot')];

Explanation:

For each slide, we create a dot element and append it to the dots container. If a dot corresponds to the current slide, it is marked as active.

4. Create updateSlide() Function

// Create a reusable function to update the slide position
        function updateSlide() {
            slider.style.transform = `translateX(-${currentIndex * 100}%)`;
            
            // Update dots
             allDots.forEach((dot, index) => {
                 if (index == currentIndex) {
                     dot.classList.add('active')
                 } else {
                     dot.classList.remove('active')
                 }
             });
        }

Explanation:

The updateSlide() function is responsible for moving the slider to the correct position based on the currentIndex. It also updates the active dot to reflect the current slide.

5. Next and Previous Slide Functions

function nextSlide() { // For next button click
        if (isAnimating) return;
        isAnimating = true;
        currentIndex = (currentIndex + 1) % slides.length;
        updateSlide()
        setTimeout(() => {
            isAnimating = false;
        }, 500)
        resetAutoSlide()
    }
    function prevSlide() { // For prev button click
        if (isAnimating) return;
        isAnimating = true;
        currentIndex = (currentIndex - 1 + slides.length) % slides.length;
        updateSlide()
        setTimeout(() => {
            isAnimating = false;
        }, 500)
        resetAutoSlide()
    }

Explanation:

The nextSlide() and prevSlide() functions move the slider to the next or previous slide, respectively. We also use setTimeout() to control animation timing and prevent multiple rapid transitions.

6. Go to Specific Slide Using Dots

function goToSlide(index) { // For dots
        if (isAnimating) return;
        isAnimating = true;
        currentIndex = index;
        updateSlide()
        setTimeout(() => {
            isAnimating = false;
        }, 500)
        resetAutoSlide()
    }

Explanation:

The goToSlide() function allows the user to jump to a specific slide when clicking on a dot. It works by setting the `currentIndex` to the corresponding dot's index.

7. Auto-slide Functionality

// Reset auto-slide on interaction
    function resetAutoSlide() {
        clearInterval(interval);
        // Re-initialize interval
        interval = setInterval(nextSlide, 3000);
    }
    // Add auto slide functionality
    interval = setInterval(nextSlide, 3000);

Explanation:

The resetAutoSlide() function stops the auto-slide timer and restarts it every time the user interacts with the slider. This ensures that the slides continue automatically after any interaction.

8. Attach Event Listeners to Buttons and Dots

nextBtn.addEventListener('click', nextSlide);
    prevBtn.addEventListener('click', prevSlide);
    allDots.forEach((dot, index) => {
        dot.addEventListener('click', () => {
            goToSlide(index)
        })
    })

Explanation:

We add event listeners to the navigation buttons and dots so that users can click to navigate between slides. Each button triggers the corresponding slide function, such as nextSlide() or goToSlide(index).

This concludes Part 1, where we created a basic slider without smooth transitions. However, in this version, clicking the next button will abruptly jump to the first slide when the last one is reached. To add smooth sliding transitions, we move on to Part 2.

Part 2: Adding Smooth Sliding Effect

1. Change const to let for Slide Selection

let slides = [...slider.querySelectorAll('.slide')];

Explanation:

We change const to let because the slides list will be updated later after adding the clone nodes.

2. Modify `currentIndex` Value

// Slider variables
    let currentIndex = 1;

Explanation:

We initialize currentIndex to 1 because we are adding a clone of the first slide at the end, so we need to start from the second slide.

3. Clone First and Last Slides for Smooth Looping

// Clone first and last slides for smooth looping
    const firstClone = slides[0].cloneNode(true);
    const lastClone = slides[slides.length - 1].cloneNode(true);
    slider.appendChild(firstClone);
    slider.insertBefore(lastClone, slides[0]);

Explanation:

To enable infinite looping, we create clones of the first and last slides and add them to the slider. This gives the illusion of a continuous slider.

4. Set Initial Position After Cloning

// Set initial position to slider after inserting clones
    slider.style.transform = `translateX(-${currentIndex * 100}%)`;
    // Update slides list
    slides = [...slider.querySelectorAll('.slide')];

Explanation:

After adding the clones, we set the initial slider position to ensure it starts at the second slide, and then we update the slides array to include the clones.

5. Ignore Cloned Dots

// Insert dots (ignore cloned slides)
    slides.forEach((slide, index) => {
        if (index === 0 || index + 1 === slides.length) {
            return;
        }
        let dot = document.createElement('span');
        dot.classList.add('dot');
        index == currentIndex && dot.classList.add('active');
        dotsContainer.appendChild(dot);
    });

Explanation:

We make sure to exclude the cloned slides from the dots, as they are not part of the original slides.

6. Update updateSlide() for Smooth Transitions

function updateSlide() {
        slider.style.transition = "transform 0.5s ease";
        slider.style.transform = `translateX(-${currentIndex * 100}%)`;

        // Update dots (with looping effect)
        allDots.forEach((dot, index) => { 
            dot.classList.remove('active');
        });

        // Correct the active dot index (since currentIndex includes clones)
        if (currentIndex === 0) {
            allDots[allDots.length - 1].classList.add("active");
        } else if (currentIndex === slides.length - 1) {
            allDots[0].classList.add("active");
        } else {
            allDots[currentIndex - 1].classList.add("active");
        }

        // Handle infinite looping effect here
        setTimeout(() => {
            if (currentIndex >= slides.length - 1) {
                slider.style.transition = "none";
                currentIndex = 1;
                slider.style.transform = `translateX(-100%)`;
                dots[0].classList.add('active');
            } else if (currentIndex <= 0) {
                slider.style.transition = "none";
                currentIndex = slides.length - 2;
                slider.style.transform = `translateX(-${currentIndex * 100}%)`;
                dots[dots.length - 1].classList.add('active');
            }
        }, 500)
    }

Explanation:

The updateSlide() function now includes the smooth transition effect and handles infinite looping. It corrects the active dot index and ensures the slider smoothly loops back to the first slide when reaching the last slide, and vice versa.

7. Update nextSlide(), prevSlide(), and goToSlide() Functions

function nextSlide() { // For next button click
        if (isAnimating) return;
        isAnimating = true;
        currentIndex++;
        updateSlide()
        setTimeout(() => {
            isAnimating = false;
        }, 500)
        resetAutoSlide()
    }
    function prevSlide() { // For prev button click
        if (isAnimating) return;
        isAnimating = true;
        currentIndex--;
        updateSlide()
        setTimeout(() => {
            isAnimating = false;
        }, 500)
        resetAutoSlide()
    }
    function goToSlide(index) { // For dots
        if (isAnimating) return;
        isAnimating = true;
        currentIndex = index + 1;
        updateSlide()
        setTimeout(() => {
            isAnimating = false;
        }, 500)
        resetAutoSlide()
    }

Explanation:

The nextSlide(), prevSlide(), and goToSlide() functions are updated to accommodate the changes in the cloned slides and to ensure the slider transitions smoothly.

With these changes, the slider now smoothly transitions between slides, and users can navigate seamlessly through them. The infinite looping effect ensures the experience feels fluid and continuous.

full code

<!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Slider Using Vanilla Js</title>
    </head>
    <style>
        *,
        *::before,
        *::after {
            padding: 0;
            margin: 0;
            box-sizing: border-box;
        }
    
        body {
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: column;
            padding: 20px;
            font-family: Arial, Helvetica, sans-serif;
            background: linear-gradient(135deg, #ff9a9e, #fad0c4);
        }
    
        body>h1 {
            text-transform: capitalize;
            color: white;
            text-shadow: 0px 2px 5px rgba(0, 0, 0, 0.373);
            margin-bottom: 1rem;
            font-size: clamp(2rem, 3vw, 2.5rem);
        }
    
        .sliderContainer {
            max-width: 600px;
            overflow: hidden;
            border-radius: 15px;
            position: relative;
            isolation: isolate;
        }
    
        .slider {
            display: flex;
            transition: 300ms ease;
        }
    
        .slider .slide {
            flex-shrink: 0;
            width: 100%;
        }
    
        .slider .slide img {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }
    
        .sliderContainer .arrows {
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            width: 96%;
            left: 2%;
            display: flex;
            justify-content: space-between;
        }
    
        .sliderContainer .arrows button {
            border: none;
            background-color: rgba(0, 0, 0, 0.495);
            color: white;
            font-size: clamp(1.2rem, 1.5vw, 1.8rem);
            width: 45px;
            height: 45px;
            display: flex;
            justify-content: center;
            align-items: center;
            border-radius: 50%;
            transition: 300ms ease;
            cursor: pointer;
        }
    
        .sliderContainer .arrows button:hover {
            background-color: rgba(0, 0, 0, 0.736);
            transform: scale(1.1);
        }
    
        .sliderContainer .arrows button:active {
            background-color: rgba(0, 0, 0, 0.884);
            transform: scale(1);
        }
    
        .sliderContainer .dots {
            display: flex;
            gap: 0.5rem;
            justify-content: center;
            align-items: center;
            position: absolute;
            bottom: 0.5rem;
            width: 100%;
            /* padding: 1rem;
            background-color: red; */
        }
    
        .sliderContainer .dots .dot {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.444);
            background-color: rgba(255, 255, 255, 0.495);
            transition: 200ms ease;
            cursor: pointer;
        }
    
        .sliderContainer .dots .dot.active {
            background-color: white;
            transform: scale(1.3);
        }
    
        .sliderContainer .dots .dot:hover {
            background-color: white;
        }
    </style>
    
    <body>
        <h1>Slider Using Vanilla Js</h1>
        <div class="sliderContainer">
            <div class="slider">
                <div class="slide"><img src="./images/img-1.webp" alt=""></div>
                <div class="slide"><img src="./images/img-2.webp" alt=""></div>
                <div class="slide"><img src="./images/img-3.webp" alt=""></div>
                <div class="slide"><img src="./images/img-4.webp" alt=""></div>
                <div class="slide"><img src="./images/img-5.webp" alt=""></div>
                <div class="slide"><img src="./images/img-6.webp" alt=""></div>
            </div>
            <!-- arrows -->
            <div class="arrows">
                <button class="prev">❮</button>
                <button class="next">❯</button>
            </div>
            <!-- navigation dots -->
            <div class="dots">
                <!-- we will add these dots using Js -->
                <!-- <span class="dot active"></span>
                <span class="dot"></span>
                <span class="dot"></span>
                <span class="dot"></span> -->
            </div>
        </div>
    
        <script>
            document.addEventListener('DOMContentLoaded', () => {
                initializeSlider();
            })
            function initializeSlider() {
                const sliderContainer = document.querySelector('.sliderContainer');
                const slider = sliderContainer.querySelector('.slider');
                let slides = [...slider.querySelectorAll('.slide')];
                const nextBtn = sliderContainer.querySelector('.arrows .next')
                const prevBtn = sliderContainer.querySelector('.arrows .prev')
                const dotsContainer = sliderContainer.querySelector('.dots');
    
                //slider variables
                // let currentIndex = 0;
                let currentIndex = 1;
                let isAnimating = false;
                let interval;
                //clone first and last slides for smooth looping
                const firstClone = slides[0].cloneNode(true);
                const lastClone = slides[slides.length - 1].cloneNode(true);
                slider.appendChild(firstClone);
                slider.insertBefore(lastClone, slides[0]);
    
                // set initial position to slider after inserting clones
                slider.style.transform = `translateX(-${currentIndex * 100}%)`;
                //update slides list
                slides = [...slider.querySelectorAll('.slide')];
                //insert dots
                slides.forEach((slide, index) => {
                    //ignore cloned nodes
                    if (index === 0 || index + 1 === slides.length) {
                        return;
                    }
                    //way 1.
                    // set first dot as active
                    // currentIndex == index 
                    // ?  dotsContainer.innerHTML += `<span class="dot active"></span>`
                    // : dotsContainer.innerHTML += `<span class="dot"></span>`;
    
                    //way 2.
                    let dot = document.createElement('span')
                    dot.classList.add('dot');
                    index == currentIndex && dot.classList.add('active');
                    dotsContainer.appendChild(dot);
                });
                // select all dots in js
                const allDots = [...dotsContainer.querySelectorAll('.dot')];
    
                // create a reusable function for updateSlide on every interaction
                function updateSlide() {
                    slider.style.transition = "transform 0.5s ease"
                    slider.style.transform = `translateX(-${currentIndex * 100}%)`;
    
                    //update dots
                    // allDots.forEach((dot, index) => {
                    //     if (index == currentIndex) {
                    //         dot.classList.add('active')
                    //     } else {
                    //         dot.classList.remove('active')
                    //     }
                    // });
    
                    //update dots (with looping effect)
                    allDots.forEach((dot,index)=>{ //nutrilize all the dots
                        dot.classList.remove('active');
                    })
    
                    //correcting the active dot index (since currentIndex includes clones)
                    if(currentIndex === 0){
                        allDots[allDots.length-1].classList.add("active");
                    }else if(currentIndex === slides.length -1){
                        allDots[0].classList.add("active");
                    }else{
                        allDots[currentIndex-1].classList.add("active");
                    }
    
                    //Handle infinit looping effect here
                    setTimeout(() => {
                        if (currentIndex >= slides.length - 1) {
                            slider.style.transition = "none";
                            currentIndex = 1;
                            slider.style.transform = `translateX(-100%)`;
                            dots[0].classList.add('active');
                        } else if (currentIndex <= 0) {
                            slider.style.transition = "none";
                            currentIndex = slides.length - 2;
                            slider.style.transform = `translateX(-${currentIndex * 100}%)`;
                            dots[dots.length - 1].classList.add('active');
                        }
                    }, 500)
                }
    
                function nextSlide() { // for next button click
                    if (isAnimating) return;
                    isAnimating = true;
                    // currentIndex = (currentIndex + 1) % slides.length
                    currentIndex++;
                    updateSlide()
                    setTimeout(() => {
                        isAnimating = false;
                    }, 500)
                    resetAutoSlide()
                }
                function prevSlide() {// for prev button click
                    if (isAnimating) return;
                    isAnimating = true;
                    // currentIndex = (currentIndex - 1 + slides.length) % slides.length
                    currentIndex--;
                    updateSlide()
                    setTimeout(() => {
                        isAnimating = false;
                    }, 500)
                    resetAutoSlide()
                }
                function goToSlide(index) {// for dots
                    if (isAnimating) return;
                    isAnimating = true;
                    // currentIndex = index;
                    currentIndex = index + 1;
                    updateSlide()
                    setTimeout(() => {
                        isAnimating = false;
                    }, 500)
                    resetAutoSlide()
                }
                // reset autoslide on interaction
                function resetAutoSlide() {
                    clearInterval(interval);
                    //re-initialize interval
                    interval = setInterval(nextSlide, 3000);
                }
                //add auto slide functionality
                interval = setInterval(nextSlide, 3000);
    
    
                nextBtn.addEventListener('click', nextSlide);
                prevBtn.addEventListener('click', prevSlide);
                allDots.forEach((dot, index) => {
                    dot.addEventListener('click', () => {
                        goToSlide(index)
                    })
                })
            }
        </script>
    </body>
    
    </html>

Tags : #JavaScript slider #infinite loop slider #vanilla JavaScript slider #JavaScript carousel #auto-slide JavaScript #smooth transition slider #JavaScript image slider #no library slider #custom JavaScript slider #how to create a slider in JavaScript

Recent blog Posts

search post

search video tutorial