Blog

JavaScript

Posted on — Last updated on July 3, 2023

How to create a simple image slider with vanilla JavaScript

Let’s build a simple Image Slideshow with vanilla JavaScript from scratch. No plugins! No dependencies!

Previously I showed you how to create a simple image slider with jQuery. This time, let’s recreate the same slider with vanilla JavaScript!

jQuery helped me learn JavaScript during my early days as a web developer and even though I don’t use it as often now I still consider it a very handy JS library. However, there are two major problems with using jQuery in your projects today:

  1. jQuery is a render-blocking resource which can affect your site’s loading times.
  2. Actually -as this tutorial hopes to demonstrate- you might not need jQuery at all.

Let’s revisit the how to create a simple image slider with jQuery tutorial except this time we’ll build our image slider using vanilla JavaScript only, removing the jQuery dependency.

Before we start

If you haven’t done so already please go check the how to create a simple image slider with jQuery tutorial first and make sure you follow all the steps up to the point where we incorporate jQuery. We won’t be using jQuery of course, instead we are going to use vanilla JavaScript -that’s the point of this post after all, haha- so once you’re done get back here to continue this (rather short) tutorial.

Also, you’ll need to update the CSS rules and change all #jquery- strings into #js-. This is important so don’t miss this step or else our Vanilla JavaScrip slider won’t work at all (see the source code of the Vanilla JavaScript Slider demo page for reference).

Done? Great, let’s continue then!

Bringing our Slider to life with JavaScript

Now that we have everything in place, it’s time to make our image slider work. As a reminder, our JS will:

  1. have our slider adapt itself to the space available automatically, and
  2. detect if there are enough slides to display pagination buttons.

We’ll start by making sure our code runs once the DOM is ready (vanilla JavaScript’s equivalent of jQuery’s ready() function):

<script>
    /** Hook into the DOMContentLoaded event to run our code */
    document.addEventListener("DOMContentLoaded", function(){
        /** Our code will live here */
    });
</script>

Next we need to declare the variables that we’ll be using in our script to manipulate our slider, namely the slider object, data on its width, and the number of items we have:

<script>
    /** Hook into the DOMContentLoaded event to run our code */
    document.addEventListener("DOMContentLoaded", function(){
        var slider = document.getElementById("jquery-slideshow"), // cache the slider object for later use
            item_width = slider.parentNode.offsetWidth, // get the width of the container for later use
            total_items = slider.querySelectorAll("li").length; // get the total number of slides for later use
    });
</script>

We want our slider to automatically adjust to the available space. You know, responsive design and all that. This has to be done on-the-fly because the user, for example, could resize the window and our slider has to be able to react to that as well. Here’s one way we can take care of it:

<script>
    /** Hook into the DOMContentLoaded event to run our code */
    document.addEventListener("DOMContentLoaded", function(){
        var slider = document.getElementById("jquery-slideshow"), // cache the slider object for later use
            item_width = slider.parentNode.offsetWidth, // get the width of the container for later use
            total_items = slider.querySelectorAll("li").length; // get the total number of slides for later use

        window.addEventListener("resize", adjust, true);
        adjust();

        // Helper functions
        function adjust(){
            item_width = slider.parentNode.offsetWidth;
            var items = slider.querySelectorAll("li");

            for (var i = 0; i < items.length; i++) {
                items[i].style.width = item_width + "px";
            }

            slider.style.width = (item_width * items.length) + "px";
        }
    });
</script>

Alright! So our slider now adjusts itself to the available space but it doesn’t do much yet. We need to add the pagination buttons back so our carousel can work.

<script>
    /** Hook into the DOMContentLoaded event to run our code */
    document.addEventListener("DOMContentLoaded", function(){
        var slider = document.getElementById("jquery-slideshow"), // cache the slider object for later use
            item_width = slider.parentNode.offsetWidth, // get the width of the container for later use
            total_items = slider.querySelectorAll("li").length; // get the total number of slides for later use
            prev_button = null, // we'll use this variable to reference the Previous button
            next_button = null; // we'll use this variable to reference the Next button

        window.addEventListener("resize", adjust, true);
        adjust();

        // Add previous/next links
        if ( total_items > 1 ) {
            prev_button = document.createElement("a");
            prev_button.setAttribute("href", "#");
            prev_button.setAttribute("id", "btn-prev");
            prev_button.innerHTML = '<i class="fa fa-angle-left"></i><span>Previous</span>';

            next_button = document.createElement("a");
            next_button.setAttribute("href", "#");
            next_button.setAttribute("id", "btn-next");
            next_button.innerHTML = '<i class="fa fa-angle-right"></i><span>Next</span>';

            slider.parentNode.appendChild(prev_button);
            slider.parentNode.appendChild(next_button);

            // Handle click on the left button
            prev_button.addEventListener("click", function(e){
                e.preventDefault();

                var elem = slider.querySelector("li:last-of-type");
                slider.insertBefore(elem, slider.querySelector("li:first-of-type"));
            });

            // Handle click on the right button
            next_button.addEventListener("click", function(e){
                e.preventDefault();

                var elem = slider.querySelector("li:first-of-type");
                slider.appendChild(elem);
            });
        }

        // Helper functions
        function adjust(){
            item_width = slider.parentNode.offsetWidth;
            var items = slider.querySelectorAll("li");

            for (var i = 0; i < items.length; i++) {
                items[i].style.width = item_width + "px";
            }

            slider.style.width = (item_width * items.length) + "px";
        }
    });
</script>

Our slider is working now! However, you’ll notice that the transition from one slide to the next is instantaneous. There’s no slide animation as with the jQuery version. That is because JavaScript doesn’t have a native version of jQuery’s very handy animate() function so we’ll need to roll up our sleeves and handle the transition ourselves.

One way we can achieve that is by using the transition CSS property in conjunction with some JS code to dynamically add/remove the transition effect whenever we need to.

First, add the following to your #js-slideshow-container #js-slideshow CSS rules:

transition: left 0.3s cubic-bezier(0.215, 0.61, 0.355, 1);

The cubic-bezier function will replace the swing easing effect provided by jQuery. The one I’m using in this example is easeOutCubic but feel free to experiment with another one.

Next, we’ll be working on the JS part of the trick so our slides are animated when transitioning between one to the other:

<script>
    /** Hook into the DOMContentLoaded event to run our code */
    document.addEventListener("DOMContentLoaded", function(){
        var slider = document.getElementById("jquery-slideshow"), // cache the slider object for later use
            item_width = slider.parentNode.offsetWidth, // get the width of the container for later use
            total_items = slider.querySelectorAll("li").length; // get the total number of slides for later use
            prev_button = null, // we'll use this variable to reference the Previous button
            next_button = null; // we'll use this variable to reference the Next button

        window.addEventListener("resize", adjust, true);
        adjust();

        // Add previous/next links
        if ( slider.querySelectorAll("li").length > 1 ) {
            prev_button = document.createElement("a");
            prev_button.setAttribute("href", "#");
            prev_button.setAttribute("id", "btn-prev");
            prev_button.innerHTML = '<i class="fa fa-angle-left"></i><span>Previous</span>';

            next_button = document.createElement("a");
            next_button.setAttribute("href", "#");
            next_button.setAttribute("id", "btn-next");
            next_button.innerHTML = '<i class="fa fa-angle-right"></i><span>Next</span>';

            slider.parentNode.appendChild(prev_button);
            slider.parentNode.appendChild(next_button);

            // Handle click on the left button
            prev_button.addEventListener("click", function(e){
                e.preventDefault();

                var elem = slider.querySelector("li:last-of-type");
                slider.insertBefore(elem, slider.querySelector("li:first-of-type"));

                // Handle transition effect
                slider.style.transitionProperty = "none";
                slider.style.left = -item_width + "px";

                setTimeout(function(){
                    slider.style.transitionProperty = "left";
                    slider.style.left = '0px';
                }, 50);
            });

            // Handle click on the right button
            next_button.addEventListener("click", function(e){
                e.preventDefault();

                slider.style.transitionProperty = "left";
                slider.style.left = -item_width + "px";

                setTimeout(function(){
                    var elem = slider.querySelector("li:first-of-type");
                    slider.appendChild(elem);

                    slider.style.transitionProperty = "none";
                    slider.style.left = '0px';
                }, 300);
            });
        }

        // Helper functions
        function adjust(){
            item_width = slider.parentNode.offsetWidth;
            var items = slider.querySelectorAll("li");

            for (var i = 0; i < items.length; i++) {
                items[i].style.width = item_width + "px";
            }

            slider.style.width = (item_width * items.length) + "px";
        }
    });
</script>

Great! We’re almost there. Now we need to keep multiple clicks on our next/previous buttons from triggering the slide animation:

<script>
    /** Hook into the DOMContentLoaded event to run our code */
    document.addEventListener("DOMContentLoaded", function(){
        var slider = document.getElementById("jquery-slideshow"), // cache the slider object for later use
            item_width = slider.parentNode.offsetWidth, // get the width of the container for later use
            total_items = slider.querySelectorAll("li").length; // get the total number of slides for later use
            prev_button = null, // we'll use this variable to reference the Previous button
            next_button = null; // we'll use this variable to reference the Next button
            animating = false;

        window.addEventListener("resize", adjust, true);
        adjust();

        // Add previous/next links
        if ( slider.querySelectorAll("li").length > 1 ) {
            prev_button = document.createElement("a");
            prev_button.setAttribute("href", "#");
            prev_button.setAttribute("id", "btn-prev");
            prev_button.innerHTML = '<i class="fa fa-angle-left"></i><span>Previous</span>';

            next_button = document.createElement("a");
            next_button.setAttribute("href", "#");
            next_button.setAttribute("id", "btn-next");
            next_button.innerHTML = '<i class="fa fa-angle-right"></i><span>Next</span>';

            slider.parentNode.appendChild(prev_button);
            slider.parentNode.appendChild(next_button);

            // Handle click on the left button
            prev_button.addEventListener("click", function(e){
                e.preventDefault();

                if ( ! animating ) {
                    animating = true;

                    var elem = slider.querySelector("li:last-of-type");
                    slider.insertBefore(elem, slider.querySelector("li:first-of-type"));

                    slider.style.transitionProperty = "none";
                    slider.style.left = -item_width + "px";

                    setTimeout(function(){
                        slider.style.transitionProperty = "left";
                        slider.style.left = '0px';
                    }, 50);

                    setTimeout(function(){
                        animating = false;
                    }, 300);
                }
            });

            // Handle click on the right button
            next_button.addEventListener("click", function(e){
                e.preventDefault();

                if ( ! animating ) {
                    animating = true;

                    slider.style.transitionProperty = "left";
                    slider.style.left = -item_width + "px";

                    setTimeout(function(){
                        var elem = slider.querySelector("li:first-of-type");
                        slider.appendChild(elem);

                        slider.style.transitionProperty = "none";
                        slider.style.left = '0px';
                    }, 300);

                    setTimeout(function(){
                        animating = false;
                    }, 300);
                }
            });
        }

        // Helper functions
        function adjust(){
            item_width = slider.parentNode.offsetWidth;
            var items = slider.querySelectorAll("li");

            for (var i = 0; i < items.length; i++) {
                items[i].style.width = item_width + "px";
            }

            slider.style.width = (item_width * items.length) + "px";
        }
    });
</script>

We’re resetting the animating variable to false after 300 milliseconds. That’s not a random number: it’s the same amount of time for the slide animation to complete we set via CSS using the transition property (transition: left 0.3s cubic-bezier(0.215, 0.61, 0.355, 1)). If you’re using different timings with your slider make sure to adjust the JS code accordingly.

Finally, we’re adding an auto-play function to our slider:

<script>
    /** Hook into the DOMContentLoaded event to run our code */
    document.addEventListener("DOMContentLoaded", function(){
        var slider = document.getElementById("jquery-slideshow"), // cache the slider object for later use
            item_width = slider.parentNode.offsetWidth, // get the width of the container for later use
            total_items = slider.querySelectorAll("li").length; // get the total number of slides for later use
            prev_button = null, // we'll use this variable to reference the Previous button
            next_button = null; // we'll use this variable to reference the Next button
            animating = false,
            timer = null;

        window.addEventListener("resize", adjust, true);
        adjust();

        // Add previous/next links
        if ( slider.querySelectorAll("li").length > 1 ) {
            prev_button = document.createElement("a");
            prev_button.setAttribute("href", "#");
            prev_button.setAttribute("id", "btn-prev");
            prev_button.innerHTML = '<i class="fa fa-angle-left"></i><span>Previous</span>';

            next_button = document.createElement("a");
            next_button.setAttribute("href", "#");
            next_button.setAttribute("id", "btn-next");
            next_button.innerHTML = '<i class="fa fa-angle-right"></i><span>Next</span>';

            slider.parentNode.appendChild(prev_button);
            slider.parentNode.appendChild(next_button);

            // Handle click on the left button
            prev_button.addEventListener("click", function(e){
                e.preventDefault();

                if ( ! animating ) {
                    animating = true;

                    var elem = slider.querySelector("li:last-of-type");
                    slider.insertBefore(elem, slider.querySelector("li:first-of-type"));

                    slider.style.transitionProperty = "none";
                    slider.style.left = -item_width + "px";

                    setTimeout(function(){
                        slider.style.transitionProperty = "left";
                        slider.style.left = '0px';
                    }, 50);

                    setTimeout(function(){
                        animating = false;
                    }, 300);
                }
            });

            // Handle click on the right button
            next_button.addEventListener("click", function(e){
                e.preventDefault();

                if ( ! animating ) {
                    animating = true;

                    slider.style.transitionProperty = "left";
                    slider.style.left = -item_width + "px";

                    setTimeout(function(){
                        var elem = slider.querySelector("li:first-of-type");
                        slider.appendChild(elem);

                        slider.style.transitionProperty = "none";
                        slider.style.left = '0px';
                    }, 300);

                    setTimeout(function(){
                        animating = false;
                    }, 300);
                }
            });
        }

        // Autoplay slider (but only when the window/tab is active
        // to save resources)
        document.addEventListener("visibilitychange", function() {
            if ( "undefined" !== typeof document.hidden && document.hidden ) {
                clearInterval(timer);
                timer = null;
            } else {
                autoplay();
            }
        }, false);

        // Resume/pause autoplay on hover in/out
        slider.addEventListener('mouseover', function(){
            if ( timer ) {
                clearInterval(timer);
                timer = null;
            }
        });

        slider.addEventListener('mouseout', function(){
            autoplay();
        });

        /** Helper functions */
        function autoplay(){
            if ( next_button ) {
                timer = setInterval(function(){
                    next_button.click();
                }, 5000);
            }
        }

        function adjust(){
            item_width = slider.parentNode.offsetWidth;
            var items = slider.querySelectorAll("li");

            for (var i = 0; i < items.length; i++) {
                items[i].style.width = item_width + "px";
            }

            slider.style.width = (item_width * items.length) + "px";
        }
    });
</script>

And with that we’re done!

Demo: Vanilla JavaScript Slider.

Questions? Suggestions? Leave a comment below!