How to Create a Draggable Carousel Using Vanilla JavaScript

Introduction

A website's carousel or slider is an effective way to display multiple images or content in a single space. It encourages visitors to concentrate on important website content while also improving overall visual appeal by saving screen space.

In this article, we will learn how to create a draggable carousel from scratch using vanilla JavaScript. This will be very detailed and explanatory so that everyone can understand it.

Note: The source code is available on GitHub.

Getting Started

To build the draggable carousel from scratch in vanilla JavaScript, we'd need to use a set of unique mouse events, such as:

  • mousedown: When a mouse button is pressed while the pointer is inside an element, the mousedown event is triggered.
  • mouseenter: When a mouse is first moved into an element, the mouseenter event is triggered.
  • mouseleave: When the cursor of a mouse moves out of an element, the mouseleave event is triggered (this is the opposite of mouseenter).
  • mouseup: The mouseup event is triggered when the pointer is within the element and a button on a mouse is released.
  • mousemove: When a mouse is moved while the cursor is inside it, the mousemove event is triggered.

Let's begin by creating our HTML file; basically, we'd have as many cards as we want with whatever content we want. To avoid pasting HTML code of more than 80 lines, I'd remove the content from the cards and rather make use of boxes:

<div class="slider-container">
    <div class="inner-slider">
        <div class="card"></div>
        <div class="card"></div>
        <div class="card"></div>
        <div class="card"></div>
        <div class="card"></div>
        <div class="card"></div>
        <div class="card"></div>
    </div>
</div>

Let’s also add some basic styles to the slider-container, inner-slider and the card:

.card {
    height: 300px;
    width: 400px;
    border-radius: 5px;
}
.card:nth-child(odd) {
    background-color: blue;
}
.card:nth-child(even) {
    background-color: rgb(0, 183, 255);
}
.slider-container {
    width: 80%;
    height: 350px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    overflow: hidden;
}
.inner-slider {
    width: 150%;
    display: flex;
    gap: 10px;
    pointer-events: none;
    position: absolute;
    top: 0;
    left: 0;
}

Note: overflow: hidden; was added to slider-container, so it hides the other cards that are outside the specified width. We also used position: absolute; alongside top and left in the inner-slider, so we can make use of the left position later with JavaScript.

At this point, your page should look like this:

Making the Carousel Draggable

The first step will be to select the slider element, the slider itself, and the slider container. Then we'll set up three variables that we'll use later.

let sliderContainer = document.querySelector('.slider-container');
let innerSlider = document.querySelector('.inner-slider');

let pressed = false;
let startX;
let x;

As previously stated, we will use a large number of mouse eventListeners to handle various operations. We will mostly be attaching them to the parent slider element.

The first event we'll watch for is the mousedown event, which is similar to but not exactly the same as a click. This is where we'll specify what we want to happen when a user clicks around the slider container.

Note: This differs from the click event in that the click event is fired after a full click action has occurred; that is, the mouse button is pressed and released while the pointer remains inside the same element. While, the mousedown is executed the moment the button is pressed for the first time.

sliderContainer.addEventListener('mousedown', () => {
    ...
})

To demonstrate that the pressed variable, which we previously initialized as false, is pressed, we will first assign true to it. We'll also set the startx value to the offset value in the x direction minus the innerSlider offset value to the left, which is currently 0. We can see what this means by attempting to log out the value of startx.

We'll also style the cursor for better interaction. This is going to be set to grabbing (to check what this does, try clicking within the slidercontainer).

sliderContainer.addEventListener("mousedown", (e) => {
    pressed = true;
    startX = e.offsetX - innerSlider.offsetLeft;
    sliderContainer.style.cursor = "grabbing";
});
Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

The next event we'd look for is mouseenter; we're doing this to add basic interactivity by styling the cursor to indicate that the slider or a specific slider card has been grabbed.

sliderContainer.addEventListener("mouseenter", () => {
    sliderContainer.style.cursor = "grab";
});

After that, we would listen for the mouseup event and set the cursor style to grab, so that when a user stops grabbing or clicking, the cursor would change back to grab rather than grabbing. We'll also return the pressed value to false.

sliderContainer.addEventListener("mouseup", () => {
    sliderContainer.style.cursor = "grab";
    pressed = false;
});

We've been able to add some interactivity up to this point, but we haven't yet implemented the main functionality, a draggable carousel.

Handling the Core Logic

Let's now take care of the core logic; we'll still be targeting the sliderContainer, but this time we'll be listening for a mousemove event. In the call back function, we would check if pressed is false so that we could return the function, and it would do nothing.

sliderContainer.addEventListener("mousemove", (e) => {
    if (!pressed) return;
    ...
});

But, if pressed is true, we can proceed to some other logics. The first step will be to prevent default behaviors, followed by setting x to offsetX (the x-coordinate of the mouse pointer relative to the container slider element).

sliderContainer.addEventListener("mousemove", (e) => {
    if (!pressed) return;
    e.preventDefault();

    x = e.offsetX;
});

You'll notice that when we styled the innerSlider CSS class, we added position: absolute and a left value of 0. Now we're going to change the left value to x-startX dynamically when the user drags the carousel. (we are subtracting our current position from the offset of the parent div).

sliderContainer.addEventListener("mousemove", (e) => {
    if (!pressed) return;
    e.preventDefault();

    x = e.offsetX;

    innerSlider.style.left = `${x - startX}px`;
});

At this point, you'll notice that everything works fine because our slider now drags properly, but there's no limit to where the scroll can stop.

Let's take care of it now by defining a new function. The first thing will be to get the position of the inner and outer slider containers, then we can now create two conditional statements to work for both sides.

const checkBoundary = () => {
    let outer = sliderContainer.getBoundingClientRect();
    let inner = innerSlider.getBoundingClientRect();

    if (parseInt(innerSlider.style.left) > 0) {
        innerSlider.style.left = "0px";
    }

    if (inner.right < outer.right) {
        innerSlider.style.left = `-${inner.width - outer.width}px`;
    }
};

Once this is done, we can now call this function within the mousemove event listener as the last thing:

sliderContainer.addEventListener("mousemove", (e) => {
    ...
    checkBoundary();
});

We were able to successfully create a draggable slider with JavaScript from scratch using this method.

Conclusion

In this article, we have learned how to create a draggable carousel from scratch using vanilla JavaScript, we also learned the difference between all the mouse events used.

Last Updated: November 8th, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Joel OlawanleAuthor

Frontend Developer & Technical Writer

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms