docode

How to Create an Animated Image Carousel with GSAP

Final Output Preview

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

📌 1. HTML Structure

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GSAP slider</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
        integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg=="
        crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
    <h1>GSAP slider</h1>
    <div class="slideContainer">
        <div class="slide">
            <div class="imgBox"><img src="./images/goku.png" alt=""></div>
            <h2>son goku</h2>
            <h2>son goku</h2>
        </div>
        <div class="slide">
            <div class="imgBox"><img src="./images/inosuke.png" alt=""></div>
            <h2>inosuke</h2>
            <h2>inosuke</h2>
        </div>
        <div class="slide">
            <div class="imgBox"><img src="./images/itachi.png" alt=""></div>
            <h2>itachi</h2>
            <h2>itachi</h2>
        </div>
        <div class="slide">
            <div class="imgBox"><img src="./images/naruto.png" alt=""></div>
            <h2>naruto</h2>
            <h2>naruto</h2>
        </div>
        <div class="slide">
            <div class="imgBox"><img src="./images/rengoku.png" alt=""></div>
            <h2>rengoku</h2>
            <h2>rengoku</h2>
        </div>
        <div class="slide">
            <div class="imgBox"><img src="./images/tanjiro.png" alt=""></div>
            <h2>tanjiro</h2>
            <h2>tanjiro</h2>
        </div>
        <div class="slide">
            <div class="imgBox"><img src="./images/zenitsu.png" alt=""></div>
            <h2>zenitsu</h2>
            <h2>zenitsu</h2>
        </div>

        <!-- arrows -->
        <div class="arrows">
            <button class="prev"><i class="fa-solid fa-angles-left"></i></button>
            <button class="next"><i class="fa-solid fa-angles-right"></i></button>
        </div>
        <!-- dots -->
        <div class="dotsContainer"></div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"
        integrity="sha512-7eHRwcbYkK4d9g/6tD/mhkf++eoTHwpNM9woBxtPUBWm67zeAfFC+HrdoE2GanKeocly/VxeLvIqwvCdk7qScg=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  </body >
</html >

Explanation:

  • Setting Up the Basic HTML Structure
  • add fontawesome and GSAP cdn
  • create slideContainer which work as wrapper of all slider elements
  • create slide which holds all the element of perticualr slide like imgBox or content h2 tags
  • create arrows for navigation
  • create dotsContainer for dot navigation and insert dots using js

📌 2. Styling CSS

Add Global Reset css and boilerplate code

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

    body,
    html {
        width: 100%;
        height: 100%;
        position: relative;
    }

Add css for body and h1

body {
        font-family: Verdana, Geneva, Tahoma, sans-serif;
        background-color: #f4f4f4;
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
        padding: 1rem;
    }

    body>h1 {
        text-align: center;
        margin: 1rem auto;
        font-size: clamp(2rem, 3vw, 4rem);
        text-transform: capitalize;
    }

Add css for slideContainer and slide and its elements

.slideContainer {
        max-width: 800px;
        background-color: black;
        color: white;
        overflow: hidden;
        position: relative;
        isolation: isolate;
        height: 100%;
        width: 100%;
    }

    .slideContainer .slide {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
    }

    .slideContainer .slide .imgBox {
        width: 100%;
    }

    .slideContainer .slide .imgBox img {
        max-height: 500px;
        max-width: 100%;
        width: fit-content;
        height: 100%;
        object-fit: contain;
    }

    .slideContainer .slide h2 {
        position: absolute;
        top: 80%;
        left: 50%;
        transform: translate(-50%, -50%);
        color: #FF00FF;
        font-size: clamp(2rem, 6vw, 8rem);
        text-transform: uppercase;
        z-index: -1;
        white-space: nowrap;
    }

    .slideContainer .slide h2:nth-child(2) {
        color: transparent;
        -webkit-text-stroke: 0.15vw white;
        z-index: 1;
    }

Add css for arrows and its buttons

.slideContainer .arrows {
        width: 100%;
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        padding: 1rem;
        display: flex;
        justify-content: space-between;
        align-items: center;
    }

    .slideContainer .arrows button {
        cursor: pointer;
        background-color: white;
        color: black;
        border: none;
        width: 45px;
        height: 45px;
        border-radius: 50%;
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 1.2rem;
        transition: 300ms ease;
    }

    .slideContainer .arrows button:hover {
        background-color: rgba(255, 255, 255, 0.733);
    }

    .slideContainer .arrows button:active {
        background-color: rgba(255, 255, 255, 0.877);
        transform: scale(0.9);
    }

Add css for dotsContainer and its dot

.slideContainer .dotsContainer {
        width: 100%;
        position: absolute;
        bottom: 0;
        left: 0;
        display: flex;
        justify-content: center;
        align-items: center;
        gap: 1rem;
        padding: 1rem;
    }

    .slideContainer .dotsContainer .dot {
        width: 25px;
        height: 25px;
        cursor: pointer;
        display: flex;
        justify-content: center;
        align-items: center;
        gap: 1rem;
        background-color: #FF00FF;
        color: white;
    }

📌 3. Add JavaScript For Slider

select all the required html elements in js

const slides = document.querySelectorAll('.slide');
    const prevBtn = document.querySelector('.arrows .prev')
    const nextBtn = document.querySelector('.arrows .next')
    const dotsContainer = document.querySelector('.dotsContainer')

create some js variables for slider

const colors = [
    "#FF00FF", // Magenta
    "#FF4500", // Neon Orange
    "#39FF14", // Neon Green
    "#FF1493", // Deep Pink
    "#FFD700", // Neon Yellow
    "#8A2BE2", // Electric Purple
    "#00FF7F", // Spring Green
    "#FF69B4", // Hot Pink
    "#1E90FF", // Dodger Blue
    "#FF6347", // Neon Tomato
    "#ADFF2F", // Green Yellow
    "#9400D3", // Dark Violet
    "#00BFFF", // Deep Sky Blue
    "#FF00FF", // Fuchsia
];
// slider variables
let currentSlideIndex = 0;
let isAnimating = false;
let autoSlideInetrval;

hide all the slides except first one by using gsap

// hide all the slides at start except first slide
gsap.set(slides, {
    autoAlpha: (i) => i == 0 ? 1 : 0
})
// slider variables
let currentSlideIndex = 0;
let isAnimating = false;
let autoSlideInetrval;

create reusable functions for slider lets call them slider functions

//show slide
    function showSlide() {

    }
    // prev slide
    function prevSlide() {

    }
    //next slide
    function nextSlide() {

    }
    // gotTo slide
    function goToSlide(index) {

    }

insert slide dots using js and select them in js

// insert dots
    slides.forEach((slide, index) => {
        let dot = document.createElement("span");
        dot.setAttribute('class', 'dot');
        let textNode = document.createTextNode(index + 1);
        dot.appendChild(textNode);
        dotsContainer.appendChild(dot);
    })
    // select all dots
    let dots = dotsContainer.querySelectorAll('.dot');

call slider functions on click of next ,prev and dot buttons

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

update slider functions code

// prev slide
    function prevSlide() {
        // handle multiple clicks
        if (isAnimating) return
        // currentSlideIndex --;
        currentSlideIndex = (currentSlideIndex - 1 + slides.length) % slides.length;
        showSlide()
        resetAutoslide()
    }
    //next slide
    function nextSlide() {
        if (isAnimating) return
        // currentSlideIndex++;
        currentSlideIndex = (currentSlideIndex + 1) % slides.length;
        showSlide()
        resetAutoslide()
    }
    // gotTo slide
    function goToSlide(index) {
        if (isAnimating) return
        currentSlideIndex = index;
        showSlide()
        resetAutoslide()
    }

update showSlide function code

updateDots(); 
    // function to show slide
    function showSlide() {
        isAnimating = true;
        //show current slide and hide others
        gsap.set(slides, {
            autoAlpha: (i) => i == currentSlideIndex ? 1 : 0
        })
        //add animation to the current slide elements
        let currentSlide = slides[currentSlideIndex];
        let ImgBox = currentSlide.querySelector('.imgBox');
        let headings = currentSlide.querySelectorAll('h2');
        gsap
            .timeline({
                onComplete: () => {
                    isAnimating = false;
                }
            })
            .from(ImgBox, {
                y: 100,
                opacity: 0,
                duration: 0.5
            }).from(headings, {
                y: -100,
                opacity: 0,
                duration: 0.5,
                delay: -0.5
            }).to(headings[1], {
                color: gsap.utils.random(colors),
                delay: -1
            })
        // update dots
        updateDots()
    }

    // update dots
    function updateDots() {
        gsap.set(dots, {
            opacity: (i) => i === currentSlideIndex ? 1 : 0.5,
            duration: 0.2,
            backgroundColor: (i) => i === currentSlideIndex ? gsap.utils.random(colors) : colors[0]
        })
    }

Explanation:

  • Prevents multiple animations by setting isAnimating = true.
  • Shows only the current slide using:

gsap.set(slides, { autoAlpha: (i) => i == currentSlideIndex ? 1 : 0 })
  • Selects key elements in the slide:

let currentSlide = slides[currentSlideIndex];
    let ImgBox = currentSlide.querySelector('.imgBox');
    let headings = currentSlide.querySelectorAll('h2');
  • Creates an animation sequence using gsap.timeline():
    • Image (ImgBox) slides up & fades in:

.from(ImgBox, { y: 100, opacity: 0, duration: 0.5 })
  • Headings (h2) slide down & fade in:

.from(headings, { y: -100, opacity: 0, duration: 0.5, delay: -0.5 })
  • Changes color of the second heading randomly:

.to(headings[1], { color: gsap.utils.random(colors), delay: -1 })
  • Marks animation as complete: isAnimating = false.
  • Calls updateDots() to update indicators.

2. updateDots() Function (Navigation Indicators)

  • Updates opacity: Active dot opacity: 1, others opacity: 0.5.
  • Changes active dot color: Picks a random color from colors array.

gsap.set(dots, {
        opacity: (i) => i === currentSlideIndex ? 1 : 0.5,
        backgroundColor: (i) => i === currentSlideIndex ? gsap.utils.random(colors) : colors[0]
    })

add functionality to autoslide

//reset autoslide
    function resetAutoslide() {
        clearInterval(autoSlideInetrval);
        autoSlideInetrval = setInterval(() => {
            nextSlide()
        }, 3000)
    }
    // auto slide
    autoSlideInetrval = setInterval(() => {
        nextSlide()
    }, 3000)

full code

<!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>GSAP slider</title>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
            integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg=="
            crossorigin="anonymous" referrerpolicy="no-referrer" />
    </head>
    <style>
        *,
        *::before,
        *::after {
            padding: 0;
            margin: 0;
            box-sizing: border-box;
        }
    
        body,
        html {
            width: 100%;
            height: 100%;
            position: relative;
        }
    
        body {
            font-family: Verdana, Geneva, Tahoma, sans-serif;
            background-color: #f4f4f4;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: column;
            padding: 1rem;
        }
    
        body>h1 {
            text-align: center;
            margin: 1rem auto;
            font-size: clamp(2rem, 3vw, 4rem);
            text-transform: capitalize;
        }
    
        .slideContainer {
            max-width: 800px;
            background-color: black;
            color: white;
            overflow: hidden;
            position: relative;
            isolation: isolate;
            height: 100%;
            width: 100%;
        }
    
        .slideContainer .slide {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }
    
        .slideContainer .slide .imgBox {
            width: 100%;
        }
    
        .slideContainer .slide .imgBox img {
            max-height: 500px;
            max-width: 100%;
            width: fit-content;
            height: 100%;
            object-fit: contain;
        }
    
        .slideContainer .slide h2 {
            position: absolute;
            top: 80%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: #FF00FF;
            font-size: clamp(2rem, 6vw, 8rem);
            text-transform: uppercase;
            z-index: -1;
            white-space: nowrap;
        }
    
        .slideContainer .slide h2:nth-child(2) {
            color: transparent;
            -webkit-text-stroke: 0.15vw white;
            z-index: 1;
        }
    
        .slideContainer .arrows {
            width: 100%;
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            padding: 1rem;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
    
        .slideContainer .arrows button {
            cursor: pointer;
            background-color: white;
            color: black;
            border: none;
            width: 45px;
            height: 45px;
            border-radius: 50%;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 1.2rem;
            transition: 300ms ease;
        }
    
        .slideContainer .arrows button:hover {
            background-color: rgba(255, 255, 255, 0.733);
        }
    
        .slideContainer .arrows button:active {
            background-color: rgba(255, 255, 255, 0.877);
            transform: scale(0.9);
        }
    
        .slideContainer .dotsContainer {
            width: 100%;
            position: absolute;
            bottom: 0;
            left: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 1rem;
            padding: 1rem;
        }
    
        .slideContainer .dotsContainer .dot {
            width: 25px;
            height: 25px;
            cursor: pointer;
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 1rem;
            background-color: #FF00FF;
            color: white;
        }
    </style>
    
    <body>
        <h1>GSAP slider</h1>
        <div class="slideContainer">
            <div class="slide">
                <div class="imgBox"><img src="./images/goku.png" alt=""></div>
                <h2>son goku</h2>
                <h2>son goku</h2>
            </div>
            <div class="slide">
                <div class="imgBox"><img src="./images/inosuke.png" alt=""></div>
                <h2>inosuke</h2>
                <h2>inosuke</h2>
            </div>
            <div class="slide">
                <div class="imgBox"><img src="./images/itachi.png" alt=""></div>
                <h2>itachi</h2>
                <h2>itachi</h2>
            </div>
            <div class="slide">
                <div class="imgBox"><img src="./images/naruto.png" alt=""></div>
                <h2>naruto</h2>
                <h2>naruto</h2>
            </div>
            <div class="slide">
                <div class="imgBox"><img src="./images/rengoku.png" alt=""></div>
                <h2>rengoku</h2>
                <h2>rengoku</h2>
            </div>
            <div class="slide">
                <div class="imgBox"><img src="./images/tanjiro.png" alt=""></div>
                <h2>tanjiro</h2>
                <h2>tanjiro</h2>
            </div>
            <div class="slide">
                <div class="imgBox"><img src="./images/zenitsu.png" alt=""></div>
                <h2>zenitsu</h2>
                <h2>zenitsu</h2>
            </div>
    
            <!-- arrows -->
            <div class="arrows">
                <button class="prev"><i class="fa-solid fa-angles-left"></i></button>
                <button class="next"><i class="fa-solid fa-angles-right"></i></button>
            </div>
            <!-- dots -->
            <div class="dotsContainer"></div>
        </div>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"
            integrity="sha512-7eHRwcbYkK4d9g/6tD/mhkf++eoTHwpNM9woBxtPUBWm67zeAfFC+HrdoE2GanKeocly/VxeLvIqwvCdk7qScg=="
            crossorigin="anonymous" referrerpolicy="no-referrer"></script>
        <script>
            const slides = document.querySelectorAll('.slide');
            const prevBtn = document.querySelector('.arrows .prev')
            const nextBtn = document.querySelector('.arrows .next')
            const dotsContainer = document.querySelector('.dotsContainer')
            const colors = [
                "#FF00FF", // Magenta
                "#FF4500", // Neon Orange
                "#39FF14", // Neon Green
                "#FF1493", // Deep Pink
                "#FFD700", // Neon Yellow
                "#8A2BE2", // Electric Purple
                "#00FF7F", // Spring Green
                "#FF69B4", // Hot Pink
                "#1E90FF", // Dodger Blue
                "#FF6347", // Neon Tomato
                "#ADFF2F", // Green Yellow
                "#9400D3", // Dark Violet
                "#00BFFF", // Deep Sky Blue
                "#FF00FF", // Fuchsia
            ];
            // hide all the slides at start except first slide
            gsap.set(slides, {
                autoAlpha: (i) => i == 0 ? 1 : 0
            })
            // slider variables
            let currentSlideIndex = 0;
            let isAnimating = false;
            let autoSlideInetrval;
            // insert dots
            slides.forEach((slide, index) => {
                let dot = document.createElement("span");
                dot.setAttribute('class', 'dot');
                let textNode = document.createTextNode(index + 1);
                dot.appendChild(textNode);
                dotsContainer.appendChild(dot);
            })
            // select all dots
            let dots = dotsContainer.querySelectorAll('.dot');
            updateDots();
            // function to show slide
            function showSlide() {
                isAnimating = true;
                //show current slide and hide others
                gsap.set(slides, {
                    autoAlpha: (i) => i == currentSlideIndex ? 1 : 0
                })
                //add animation to the current slide elements
                let currentSlide = slides[currentSlideIndex];
                let ImgBox = currentSlide.querySelector('.imgBox');
                let headings = currentSlide.querySelectorAll('h2');
                gsap
                    .timeline({
                        onComplete: () => {
                            isAnimating = false;
                        }
                    })
                    .from(ImgBox, {
                        y: 100,
                        opacity: 0,
                        duration: 0.5
                    }).from(headings, {
                        y: -100,
                        opacity: 0,
                        duration: 0.5,
                        delay: -0.5
                    }).to(headings[1], {
                        color: gsap.utils.random(colors),
                        delay: -1
                    })
                // update dots
                updateDots()
            }
            // prev slide
            function prevSlide() {
                // handle multiple clicks
                if (isAnimating) return
                // currentSlideIndex --;
                currentSlideIndex = (currentSlideIndex - 1 + slides.length) % slides.length;
                showSlide()
                resetAutoslide()
            }
            //next slide
            function nextSlide() {
                if (isAnimating) return
                // currentSlideIndex++;
                currentSlideIndex = (currentSlideIndex + 1) % slides.length;
                showSlide()
                resetAutoslide()
            }
            // gotTo slide
            function goToSlide(index) {
                if (isAnimating) return
                currentSlideIndex = index;
                showSlide()
                resetAutoslide()
            }
            // update dots
            function updateDots() {
                gsap.set(dots, {
                    opacity: (i) => i === currentSlideIndex ? 1 : 0.5,
                    duration: 0.2,
                    backgroundColor: (i) => i === currentSlideIndex ? gsap.utils.random(colors) : colors[0]
                })
            }
            //reset autoslide
            function resetAutoslide() {
                clearInterval(autoSlideInetrval);
                autoSlideInetrval = setInterval(() => {
                    nextSlide()
                }, 3000)
            }
            // auto slide
            autoSlideInetrval = setInterval(() => {
                nextSlide()
            }, 3000)
            nextBtn.addEventListener('click', nextSlide)
            prevBtn.addEventListener('click', prevSlide)
            dots.forEach((dot, index) => {
                dot.addEventListener('click', () => {
                    goToSlide(index);
                })
            })
        </script>
    </body>
    
    </html>
Tags JavaScript slider infinite loop slider JavaScript carousel auto-slide JavaScript smooth transition slider JavaScript image slider how to create a slider in JavaScript GSAP slider GSAP animations image carousel slide transitions and interactive navigation how to create a slider in GSAP