How To Build an Animated Slider With Vanilla JavaScript

Updated on:

This guide demonstrates how to create a basic, lightweight slider (also known as a ‘carousel’) with animation using pure JavaScript.

Let’s talk about that. You know, those nifty sliding showcases make our websites pop! They’re like the Swiss Army knives of web design, perfect for when you’ve got tons of cool stuff to show off but not a lot of space to do it in. Whether you’re flipping through pics, showing off what your happy customers are saying, or cycling through your latest updates, carousels have got your back!

So, how about we roll up our sleeves and build our own carousel? Trust me, it’s not as scary as it sounds! In this fun little tutorial, we’re going to whip up an awesome, accessible carousel using nothing but good ol’ HTML, CSS, and vanilla JavaScript. Don’t worry if you’re not a coding wizard – as long as you’ve got the basics down, you’re good to go!

Ready to dive in and create something cool? Let’s do this! And hey, if you want to peek at the finished product, I’ve got all the code waiting for you over on GitHub repository. Let’s make some carousel magic happen!

1. Setup environment: VS Code, Node.js and Gulp

Before we dive into the fun stuff, we need to set up our dev environment. Don’t worry, it’s not as scary as it sounds!

We’ll be using four awesome tools to make our lives easier:

  1. VS Code – a super cool code & text editor
  2. Node.js – a JavaScript runtime environment
  3. Yarn – a package manager
  4. Gulp – a task manager for automating our workflow
  5. Browser-sync – a live updating web server that refreshes after code changes

Plus, we’ll throw in a few handy utilities to spice things up!

Ready to get these tools up and running? Let’s roll up our sleeves and turn your computer into a slider-making machine!

First create a project folder, such as “slider“, then add the file “package.json” to it. This file identifies the project and lists the packages your project depends on.

{
  "main": "gulpfile.js",
  "type": "module",
  "devDependencies": {
    "browser-sync": "^3.0.2",
    "gulp": "^5.0.0",
    "gulp-autoprefixer": "^9.0.0",
    "gulp-cached": "^1.1.1",
    "gulp-debug": "^5.0.1",
    "gulp-rimraf": "^1.0.0",
    "gulp-sass": "^5.1.0",
    "path-exists": "^5.0.0",
    "sass": "^1.77.6"
  }
}

Go to the terminal and run command “yarn install“, it should install all relative nodejs packages for our project.

Next, create two subfolders:

  • src” – this folder should contain the source codes for the slider, including both js and scss files
  • build” – this folder will store the final compiled slider js and css files after Gulp has processed them
  • demo” – this folder will contain a demo HTML page, along with any images or assets needed to test and showcase the functionality of our slider script

2. Create HTML markup for Slider

Alright, let’s get our hands dirty and dive into the code! We’re about to build an awesome slider using HTML. We’ll also add some nav controls for our slider to make it interactive. Here’s a sneak peek at how our markup structure will look in “index.html“. Place this file into the folder “demo“, also add some PNG images with names 01.png, 02.png, 03.png, 04.png for test purpose.

<html>
<head>
    <link rel="stylesheet" href="../build/slider.css" />
    <script type="text/javascript" src="../build/slider.js"></script>
</head>
<body>
    <div class="slider">
        <div class="slider-slides">
            <div class="slider-slide"><img src="01.png" /></div>
            <div class="slider-slide"><img src="02.png" /></div>
            <div class="slider-slide"><img src="03.png" /></div>
            <div class="slider-slide"><img src="04.png" /></div>
        </div>
        <div class="slider-nav">
            <div class="slider-prev"><</div>
            <div class="slider-next">></div>
        </div>
    </div>
</body>
</html>

To kick things off, we’ve included references to the future js and css files of our slider in the “head” section of our HTML document. These files will be processed and built by the Gulp task manager later on.

The structure of our slider is designed to be as simple as possible. It consists of a main container with the class “slider“, which inlcudes two sub-containers: “slider-slides” and “slider-nav“. The “slider-slides” container is where the magic happens, housing our individual slides with images. On the other hand, “slider-nav” takes care of navigation controls, providing “prev” and “next” buttons to navigate through the slides.

3. Style the Slider With SCSS

Let’s get to the design of our slider! To keep things simple and straightforward, in this tutorial I will use simple css rules to design the slides. But remember, you can add any styles and elements to the carousel!

Create a new file “slider.scss” in the “src” folder and copy and paste styles that shown below.

.slider {
    position: relative;
    width: 100%;
    height: 400px;
    margin: 0 auto;
    background-color: rgba(#000, .2);
    
    &.slider-ready {
        .slider-nav {
            opacity: 1;
            pointer-events: auto;
        }
    }
}

.slider-slides {
    .slider-slide {
        position: absolute;
        width: 100%;
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        pointer-events: none;
        opacity: 0;

        img {
            padding: 5px;
            background-color: #fff;
            max-width: 90%;
            max-height: 90%;
        }

        &.slider-active {
            pointer-events: auto;
            opacity: 1;
        }
    }
}

.slider-nav {
    width: 100%;
    height: 100%;
    pointer-events: none;
    opacity: 0;

    .slider-prev,
    .slider-next {
        transition: background-color .3s;
        position: absolute;
        top: 50%;
        transform: translate(0, -50%);
        padding: 10px 15px;
        color: #fff;
        background-color: rgba(#000, .5);
        cursor: pointer;

        &:hover {
            background-color: rgba(#000, 1);
        }
    }
    .slider-prev {
        left: 0;
    }
    .slider-next {
        right: 0;
    }
}

As you can see, the slider has a main container that contains another container for slides. Each slide completely fills the space of its parent for this we use “position:absolute“, “width:100%” and “height:100%“. Using the styles “pointer-events:none” and “opacity:0” we hide the slides by default, since we should only show one slide at a time, all others should be hidden. Also, the “display:flex” style with “align-items:center” and “justify-content:center” is applied to center the inner image. Plus the image itself has styles “max-width:90%” “max-height:90%“, so it will be clearly inside its container without going beyond its borders. As a result, we have a responsive layout that will look good both on desktop and mobile devices.

4. Add Basic Functionality With JavaScript

Let’s write a little bit of basic js code that we’ll use to walk through all the slides on the HTML page and just set the “slider-ready” class for them. While in the “src” folder, create a slider.js file and copy the code below.

const slider = function() {
    this.init = () => {
        // find all unready sliders and turn them on
        const $sliders = document.querySelectorAll('.slider:not(.slider-ready)');
        for(const $slider of $sliders) {
            $slider.classList.add('slider-ready');
        }
    }

    return this;
}();


window.onload = () => {
    slider.init();
};

Basically, we are halfway there, now we need to build our project and make sure that everything works. Let’s do it.

5. Setup Gulp Project

If we have done everything correctly, we have the following folder and file structure.

- build
- demo
   - index.html
   - 01.png
   - 02.png
   - 03.png
   - 04.png
- src
   - slider.js
   - slider.scss
package.json
yarn.lock

Now we have to use gulp to build and run our project. The files from the “src” folder will be processed and placed in the “build” folder, plus the local browser will be launched and we can modify the code on the fly and it will be automatically applied to the page.

Let’s say some words about Gulp. It is a popular build tool that can streamline your workflow and save you time. But, like any powerful tool, there’s a learning curve. This tutorial will take you on a journey, covering the basics of Gulp and how to set it up for our current front-end project.

So, why is Gulp such a big deal?

Well, Gulp automates tedious tasks, allowing you to focus on more important things. For example, it can compile Sass to CSS, combine multiple JavaScript files, and minify your CSS and JavaScript, all while watching for file changes and automatically updating.

And that’s just the beginning – Gulp can handle much more complex tasks, too. But for now, let’s keep it simple and get you comfortable with the basics.

Create the “gulpfile.js” file in the root directory where the “package.json” is, then fill it as shown below. It’s just js code that run our tasks.

'use strict';

import gulp from 'gulp';
import rimraf from 'gulp-rimraf';
import cached from 'gulp-cached';
import debug from 'gulp-debug';
import autoprefixer from 'gulp-autoprefixer';
import * as sass from 'sass'
import gsass from 'gulp-sass';
import {pathExistsSync} from 'path-exists';
import browserSync from 'browser-sync';

const config = {
    demo: 'demo',
    build: 'build',
    routes: {
        scss: {
            src: ['src/**/*.scss'],
            watch: ['src/**/*.scss']
        },
        js: {
            src: ['src/**/*.js'],
            watch: ['src/**/*.js']
        },
        demo: {
            watch: ['demo/**/*.html',  'demo/**/*.css']
        }
    }
}

const do_clean = (done) => {
    if(pathExistsSync(config.build)) {
        return gulp.src([config.build + '/*'], { read: false }).pipe(rimraf({ force: true }));
    }
    done();
}

const do_browserSync = (done) => {
    browserSync({
        server: {
            baseDir: '.'
        },
        startPath: config.demo
    });
    done();
}

const do_reload = (done) => {
    browserSync.reload();
    done();
}

const do_scss = () => {
    return gulp.src(config.routes.scss.src, { base: 'src' })
    .pipe(cached('cache_scss'))
    .pipe(debug())
    .pipe(gsass(sass)())
    .pipe(autoprefixer())
    .pipe(gulp.dest(config.build));
}

const do_js = () => {
    return gulp.src(config.routes.js.src, { base: 'src' })
    .pipe(cached('cache_js'))
    .pipe(debug())
    .pipe(gulp.dest(config.build));
}

const do_watch = (done) => {
    gulp.watch(config.routes.scss.watch, gulp.series(do_scss, do_reload));
    gulp.watch(config.routes.js.watch, gulp.series(do_js, do_reload));
    gulp.watch(config.routes.demo.watch, gulp.series(do_reload));
    done();
}

const build = gulp.series(
    do_clean,
    do_scss,
    do_js,
    do_watch,
    do_browserSync
)

export default build;

Let’s quickly go through its parts to make things clearer.

First of all, we import all the necessary packages so we can utilize their functions and objects within our Gulpfile script. This allows us to access and incorporate the package functionalities into our build system, making the workflow more efficient and automated. I won’t describe each package, you can read about them at https://www.npmjs.com/, plus the code is small and it shows everything clearly. Let’s describe only the main points.

  • config” – this contains the settings for the builder, including the locations of the “src” and “build” folders, and configurations for the Gulp file system watcher to monitor specific types of files for changes.
  • do_clean” – this task clears the “build” directory to prepare for a new build, ensuring a clean starting point.
  • do_browserSync” – this task involves running a local server to test the plugin with hot updates, allowing for real-time testing and automatic updates when changes are made.
  • do_reload” – this task handles the hot updates by reloading the browser whenever monitored files are modified.
  • do_scss” – this task converts SCSS (Sassy CSS) files to CSS and places the output in the “build” folder.
  • do_js” – this task processes JavaScript files and places them in the “build” folder.
  • do_watch” – this task watches for changes in the specified files and triggers a browser reload if needed, ensuring that changes are reflected immediately.
  • build” – this task combines and composes the previously defined tasks, creating a larger, more complex operation that is executed sequentially to ensure a precise build process.

If everything is done correctly, you should go to the terminal and run the gulp command.

After executing the command, a browser with our slider will be opened. It should look like in the picture below, just gray placeholder and navigation buttons, because all our slides has “opacity:0” style.

For example, if we change the background color for the root container in the “slider.scss” file, then gulp should notice our changes and apply them to the final page by refreshing it.

.slider { 
   background-color: rgba(#bdd9bf, .2); 
}

The new background color has been applied immediately.

6. Bring Your Slider to Life With JavaScript

Let’s add some fun to functionality! This slider includes two crucial logical components:

  • Go to the next slide seamlessly when the forward arrow is clicked
  • Go to the previous slide effortlessly when the backward arrow is selected

For this we should create the “build” function. In it we will assign click handlers to the “prev” and “next” buttons, display the first not active slide with an image and set the slider class “slider-ready“, which means that the slider is ready for use.

const build = ($slider) => {
    // bind nav controls
    const $prev = $slider.querySelector('.slider-nav .slider-prev');
    const $next = $slider.querySelector('.slider-nav .slider-next');

    $prev.addEventListener('click', prev);
    $next.addEventListener('click', next);

    // get the first slide and show it
    const $slide = $slider.querySelector('.slider-slides .slider-slide');
    show($slide);

    // we're ready
    $slider.classList.add('slider-ready');
}

Next, some words about “show” function. It has very simple functionality. We need find active slide, remove class “slider-active” and add this class to the slide that passed via parameter. That’s all.

const show = ($slide) => {
    if(!$slide) return;

    // hide currect active slide
    const $slideActive = $slide.parentNode.querySelector('.slider-slide.slider-active');
    if($slideActive) {
        $slideActive.classList.remove('slider-active');
    }
        
    // show new slide
    $slide.classList.add('slider-active');
}

The last part is our click handlers, which are called when the user clicks on our “prev” and “next” navigation buttons.

When the user clicked on the button, we need to find our slider so that we can set the right classes for the slides afterwards. For this we will use the “closest” function, it is similar to the jQuery method of the same name, but in our case it uses pure js.

// find the closest element
const closest = (el, selector) => {       
    do {
        if(Element.prototype.matches.call(el, selector)) return el;
        el = el.parentNode;
    } while (el !== null && el.nodeType === 1); // nodeType === Node.ELEMENT_NODE
    
    return null;
}

Once we have found our parent slider, we need to hide the current slide in it, and show either the previous or next slide. To do this, let’s write the goto($slider, direction) function, where direction is the “prev” or “next” direction.

const goto = ($slider, direction) => {
    const $slides = $slider.querySelectorAll('.slider-slide');
    for(let i=0; i<$slides.length; i++) {
        if($slides[i].classList.contains('slider-active')) {
            if (direction == 'prev') {
                i = i>0 ? i-1 : $slides.length-1;
            } else if (direction == 'next') {
                i = i<$slides.length-1 ? i+1 : 0;
            }
            show($slides[i]);
            break;
        }
    }
}

After that, we’ll just add this function to our “prev” and “next” handlers.

// go to the prev slide
const prev = (e) => {
    const $slider = closest(e.target, '.slider');
    goto($slider, 'prev');
}

// go to the next slide
const next = (e) => {
    const $slider = closest(e.target, '.slider');
    goto($slider, 'next');
}

All the Code

Combining the above JavaScript code, we should have a slider. So far it has no animation, but the main functionality, namely the change of slides is already realized in it.

const slider = function() {
    this.init = () => {
        // find all unready sliders and turn them on
        const $sliders = document.querySelectorAll('.slider:not(.slider-ready)');
        for(const $slider of $sliders) {
            build($slider);
        }
    }

    const build = ($slider) => {
        // bind nav controls
        const $prev = $slider.querySelector('.slider-nav .slider-prev');
        const $next = $slider.querySelector('.slider-nav .slider-next');

        $prev.addEventListener('click', prev);
        $next.addEventListener('click', next);

        // get the first slide and show it
        const $slide = $slider.querySelector('.slider-slides .slider-slide');
        show($slide);

        // we're ready
        $slider.classList.add('slider-ready');
    }

    const show = ($slide) => {
        if(!$slide) return;

        // hide currect active slide
        const $slideActive = $slide.parentNode.querySelector('.slider-slide.slider-active');
        if($slideActive) {
            $slideActive.classList.remove('slider-active');
        }
        
        // show new slide
        $slide.classList.add('slider-active');
    }

    const goto = ($slider, direction) => {
        const $slides = $slider.querySelectorAll('.slider-slide');
        for(let i=0; i<$slides.length; i++) {
            if($slides[i].classList.contains('slider-active')) {
                if (direction == 'prev') {
                    i = i>0 ? i-1 : $slides.length-1;
                } else if (direction == 'next') {
                    i = i<$slides.length-1 ? i+1 : 0;
                }
                show($slides[i]);
                break;
            }
        }
    }

    // find the closest element
    const closest = (el, selector) => {       
        do {
            if(Element.prototype.matches.call(el, selector)) return el;
            el = el.parentNode;
        } while (el !== null && el.nodeType === 1); // nodeType === Node.ELEMENT_NODE
        
        return null;
    }

    // go to the prev slide
    const prev = (e) => {
        const $slider = closest(e.target, '.slider');
        goto($slider, 'prev');
    }

    // go to the next slide
    const next = (e) => {
        const $slider = closest(e.target, '.slider');
        goto($slider, 'next');
    }

    return this;
}();


window.onload = () => {
    slider.init();
};

6. Let’s animate the slider

Our slider is functional, but it’s time to infuse motion and bring it to life! We’ll embark on an animation adventure by harnessing the power of the animate.css library. Get ready to add that extra flair and engage your audience with captivating visual effects!

Go to the “demo” folder, open the “index.html” file and include the animate.min.css file in the head block. After this we can use all predefined animation from this awesome library.

<html>
<head>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
    <link rel="stylesheet" href="../build/slider.css" />
    <script type="text/javascript" src="../build/slider.js"></script>
</head>

The animation styles are now integrated, and with just a few lines of JavaScript code, we’ll ignite the magic of motion. Get ready to witness your slider transform into a captivating display, captivating users with every transition.

Go to the slider.js file, find “build” function and for all slide images add class “animate__animated“.

const build = ($slider) => {
    // bind nav controls
    //...

    // init animation for images
    const $images = $slider.querySelectorAll('.slider-slides .slider-slide img');
    for(const $image of $images) {
        $image.classList.add('animate__animated');
    }

    // get the first slide and show it
    //...
    
    // we're ready
    $slider.classList.add('slider-ready');
}

The last step is to modify the “show” function. We need to add animation class “animate__jackInTheBox” to the slide image that will be shown, and remove this class from the image that will be hidden. Why remove it, you may ask? In order for the animation to be reapplied again, otherwise it will only be done once the first time it is shown.

const show = ($slide) => {
    if(!$slide) return;

    const animationClass = 'animate__jackInTheBox';

    // hide currect active slide
    const $slideActive = $slide.parentNode.querySelector('.slider-slide.slider-active');
    if($slideActive) {
        $slideActive.classList.remove('slider-active');
        $slideActive.querySelector('img').classList.remove(animationClass);
    }
	
    // show new slide
    $slide.classList.add('slider-active');

    // enable show animation
    $slide.querySelector('img').classList.add(animationClass);
}

If everything is added correctly, gulp will refresh our page and we will see the final working variant of our slider with animation. Cheers!

Conclusion

With that, we’ve reached the end of this animated slider tutorial! While we’ve covered the fundamentals, there’s certainly room for expansion. If you’re feeling adventurous, you can explore additional features with CSS intricacies, or extra js code with the power of third-party libraries.

All code is avalible on GitHub repository under MIT license. Dive in and continue your journey toward creating captivating sliders!