Vanilla JavaScript: How to Create a Draggable Slider with Auto-Play and Navigation - Part Two

lynxdev32

Ayobami Ajayi

Posted on October 17, 2023

Vanilla JavaScript: How to Create a Draggable Slider with Auto-Play and Navigation - Part Two

A draggable slider enables users to scroll through its contents by dragging with a mouse or a similar input device, enhancing user interaction and information-sharing efficiency.

Moreover, when implementing draggable sliders on mobile devices, it’s common to provide alternative navigation methods like swiping. This approach ensures a smooth user experience across various devices.

With that in mind, this article explains how to implement the following features for our slider using plain HTML, CSS, and JavaScript:

  1. Autoplay functionality
  2. Dragging option for non-mobile devices
  3. Swiping option for mobile devices.

Codepen here:

This article also serves as a sequel to my previous piece on this topic, where I extensively covered the HTML and CSS aspects. Although I’ll provide the starter code (HTML and CSS) here, I recommend reviewing the prior article to ensure we’re on the same page.

Prerequisites

To get the most out of this article, you should have:

  • A basic understanding of HTML and CSS.
  • A basic understanding of JavaScript and DOM manipulation.

Setting Things Up

Paste the following HTML and CSS codes in your editor to get started.

The HTML

In the body element, we’ll create a slider section to showcase our slides. This container will include:

  1. Four unique radio inputs, one for each slide.
  2. A ul with the class slides-flex containing four li elements. Each li represents an individual slide and carries the class slide.
  3. A div with the navigation class housing four label elements. Each label element is linked to one of the radio inputs described earlier and has a unique class and an id ranging from 1 to 4.
<body>
    <section class="slider">
      <!-- radio buttons start -->
      <input type="radio" name="radio-btn" id="radio1" checked />
      <input type="radio" name="radio-btn" id="radio2" />
      <input type="radio" name="radio-btn" id="radio3" />
      <input type="radio" name="radio-btn" id="radio4" />
      <!-- radio buttons end -->
      <!-- slides start -->
      <ul class="slides-flex">
        <li class="slide">
          <h2>1</h2>
        </li>
        <li class="slide">
          <h2>2</h2>
        </li>
        <li class="slide">
          <h2>3</h2>
        </li>
        <li class="slide">
          <h2>4</h2>
        </li>
      </ul>
      <!-- slides end -->
      <!-- navigation start -->
      <div class="navigation">
        <label for="radio1" class="btn1" id="1"></label>
        <label for="radio2" class="btn2" id="2"></label>
        <label for="radio3" class="btn3" id="3"></label>
        <label for="radio4" class="btn4" id="4"></label>
      </div>
      <!-- navigation end -->
    </section>
  </body>
Enter fullscreen mode Exit fullscreen mode

The output:

HTML structure of our slider

With the HTML structure in place, let’s add in some styling.

The CSS

Here we’ll:

  1. Center our slider using CSS grid.
  2. Hide the radio inputs.
  3. Style .slider as our container with responsive dimensions and relative positioning.
  4. Use flexbox to horizontally arrange .slides-flex and add absolute positioning to enable smooth sliding within .slider.
  5. Ensure each .slide fills the entire container’s width, displaying only one slide at a time.
  6. Style navigation labels to mimic radio inputs.
  7. Utilize the :checked pseudo-class to adjust .slides-flex’s position and change navigation label backgrounds based on the selected radio input.
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

:root {
  --primaryColor: #333;
}

body {
  display: grid;
  height: 100vh;
  place-items: center;
  background-color: rgb(208, 230, 249);
  font-family: "Poppins", sans-serif;
}

ul {
  list-style: none;
}

.slider {
  position: relative;
  width: 80%;
  aspect-ratio: 1;
  max-width: 50rem;
  max-height: 31.25rem;
  border-radius: 0.7rem;
  overflow: hidden;
  cursor: grab;
}

.slider input {
  display: none;
}

/* SLIDES */
.slides-flex {
  position: absolute;
  left: 0;
  width: 400%;
  height: 100%;
  display: flex;
  transition: 0.8s;
}

.slide {
  width: 25%;
  display: grid;
  place-items: center;
}

.slide:nth-child(1) {
  background-color: #fed050;
  border-radius: 0.7rem 0 0 0.7rem;
}

.slide:nth-child(2) {
  background-color: #7fdbee;
}

.slide:nth-child(3) {
  background-color: #4cf89c;
}

.slide:nth-child(4) {
  background-color: #f79191;
  border-radius: 0 0.7rem 0.7rem 0;
}

.slide h2 {
  user-select: none;
  color: var(--primaryColor);
  height: 5rem;
  width: 5rem;
  font-size: 3rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  text-align: center;
  outline: 3px solid;
}

/* NAVIGATION */
.navigation {
  position: absolute;
  user-select: none;
  width: 100%;
  bottom: 2.5rem;
  display: flex;
  gap: 2rem;
  justify-content: center;
}

.navigation label {
  border: 2px solid var(--primaryColor);
  padding: 0.3rem;
  border-radius: 50%;
  cursor: pointer;
  transition: 1s;
}

/* ON RADIO CHECKED */
#radio1:checked ~ .slides-flex {
  left: 0%;
}

#radio2:checked ~ .slides-flex {
  left: -100%;
}

#radio3:checked ~ .slides-flex {
  left: -200%;
}

#radio4:checked ~ .slides-flex {
  left: -300%;
}

#radio1:checked ~ .navigation .btn1 {
  background: var(--primaryColor);
}

#radio2:checked ~ .navigation .btn2 {
  background: var(--primaryColor);
}

#radio3:checked ~ .navigation .btn3 {
  background: var(--primaryColor);
}

#radio4:checked ~ .navigation .btn4 {
  background: var(--primaryColor);
}
Enter fullscreen mode Exit fullscreen mode

The output:

HTML and CSS portion of our slider

With the setup complete, let’s proceed to the JavaScript portion.

Selecting DOM Elements

Our first task is to select the HTML elements we’ll work with and assign them to variables using the querySelector method:

const slideContainer = document.querySelector(".slider");
const slideFlex = document.querySelector(".slides-flex");
const navigation = document.querySelector(".navigation");
Enter fullscreen mode Exit fullscreen mode

IIFEs

An IIFE (Immediately Invoked Function Expression) is a JavaScript function that executes as soon as it is defined. Its typical syntax is:

(function (){
  // some code
})()
Enter fullscreen mode Exit fullscreen mode

IIFEs establish a private scope, thereby preventing the issue of namespace pollution.

Namespace pollution occurs when global variables or functions lead to conflicts in your code, especially within large projects.

To address this issue and promote code reusability, we’ll create an IIFE named slider. This function will take our selected elements as arguments and encapsulate the remainder of our code:

(function slider(container, slideFlex, navigation){
  // our code
})(slideContainer, slideFlex, navigation)p
Enter fullscreen mode Exit fullscreen mode

Setting Initial Values

In the slider IIFE, we’ll define initial variables that we can access and modify later in our code:

let initialX,
    finalX,
    leftPos = -100,
    isDragging = false,
    slideDistance,
    interval = null,
    slidesWidth = slideFlex.offsetWidth,
    numberOfSlides = slideFlex.childElementCount,
    counter = 0,
    threshold = 40;
Enter fullscreen mode Exit fullscreen mode

These variables serve the following purposes:

  1. initialX: Stores the user’s initial horizontal position when clicking or tapping.
  2. finalX: Stores the user’s final horizontal position after dragging or swiping.
  3. leftPos: Initialized at -100. We’ll use this value to adjust the left property of slideFlex in the CSS, either by addition or subtraction, to shift the slides.
  4. isDragging: Keeps track of user intent, set to false when not dragging and true when the user intends to drag.
  5. slideDistance: Stores the horizontal distance of swipes or drags within the slider.
  6. interval: initialized at null to avoid interval conflicts. It will store the returned interval ID of our upcoming setInterval() function.
  7. slidesWidth: Monitors the current width of slideFlex in pixels.
  8. numberOfSlides: Determines the number of slides within slideFlex.
  9. counter: Keeps track of the currently displayed slide. Starts at 0 and ends at numberOfSlides - 1.
  10. threshold: Initializes at 40, representing the minimum swipe or drag percentage required for a slide switch.

Setting Event Listeners

Event listeners are functions that respond to user events or interactions on a specific HTML element.

In this section, I’ll outline the various event listeners that enable user interaction with our slider.

Initiate interval

document.addEventListener("DOMContentLoaded", createInterval);
Enter fullscreen mode Exit fullscreen mode

In the above code, we attach an event listener to the entire webpage using the document object. This listener responds to the “DOMContentLoaded” event, which indicates the full loading and parsing of the HTML document.

As a result, this event listener ensures that all page elements are available and accessible before the associated function executes, thereby enhancing webpage performance.

The listener triggers the callback function, createInterval, which, in turn, enables the autoplay feature.

Mouse events

container.addEventListener("mousedown", dragStart);
Enter fullscreen mode Exit fullscreen mode

Here, we attach a “mousedown” event listener to the container argument, which serves as our slider container.

The “mousedown” event activates when the user presses a mouse button. It signifies the user’s intent to initiate a drag action and subsequently triggers the dragStart function to handle this interaction.

Touch events

Touch and mouse events share many similarities, but significant differences affect how they interpret user interaction.

Notably, touch events always target the element initially tapped, whereas mouse events target the element currently under the cursor.

This distinction becomes especially evident during “touchmove” and “mousemove” events. “touchmove” continues to trigger even when the touch moves outside the element, whereas “mousemove” ceases as soon as the mouse pointer exits the element’s boundaries.

To ensure consistent behavior for our slider on both touch and mouse-based platforms, we must adjust our event listeners accordingly:

container.addEventListener("touchstart", dragStart);
container.addEventListener("touchmove", dragging);
container.addEventListener("touchend", dragStop);
Enter fullscreen mode Exit fullscreen mode

In this code, we attach three distinct event listeners to the container element:

  1. “touchstart”: Activates when users touch the element and triggers the dragStart callback function.
  2. “touchmove”: Activates when users swipe along the element and triggers dragging.
  3. “touchend”: Activates when the user stops touching the screen and triggers dragEnd .

If you’re curious about the mouse-based alternatives to our “touchmove” and “touchend” events, remember that we aim to avoid sudden interruptions when the mouse pointer exits the element’s boundaries. To achieve this, we must connect the “mousemove” and “mouseup” event listeners to the entire webpage.

However, it’s essential to declare these mouse-based listeners only after the user has initiated a drag action. Therefore, we’ll incorporate the additional listeners within dragStart .

Functions and Callbacks

In JavaScript, it’s considered best practice to modularize your code. Hence, we’ll break our code into separate functions, each responsible for distinct tasks.

In this section, I’ll outline the various functions and callback functions used in our slider.

moveSlide()

As its name suggests, this function is responsible for shifting the slides. The code for this function is as follows:

function moveSlide() {
    slidesFlex.style.left = `${leftPos * counter}%`;
    document.getElementById("radio" + (counter + 1)).checked = true;
  }
Enter fullscreen mode Exit fullscreen mode

The function:

  1. Updates the position of slidesFlex by multiplying leftPos (-100) with the current value of counter.
  2. Checks the radio input at that position, so we know what slide we’re on.

animate()

This function automates slide shifts, and calling it repeatedly enables the autoplay feature. Here’s the code:

function animate() {
    counter++;
    if (counter > (numberOfSlides - 1)) {
      counter = 0;
      slidesFlex.style.transition = "none";
    } else {
      slidesFlex.style.transition = "0.8s";
    }
    moveSlide();
  }
Enter fullscreen mode Exit fullscreen mode

The function:

  1. Increments counter to move to the next slide, and resets it when it reaches the last slide.
  2. Adjusts the transition time of slides between the last and first slide to provide smoother motion for a better user experience.
  3. Calls moveSlide() to shift the slides.

createInterval()

This function establishes the interval that repeatedly calls our animate() function. Here’s the code:

function createInterval() {
    if (!interval) {
      interval = setInterval(animate, 5000);
    }
  }
Enter fullscreen mode Exit fullscreen mode

The function:

  1. Checks that another interval isn’t running before proceeding.
  2. Utilizes the setInterval() function to call animate() every five seconds.

At this point, we’ve successfully implemented the autoplay feature, causing our slides to switch automatically every five seconds:

our slider with autoplay

dragStart()

This callback function triggers when a user interacts with the container. It has a dual purpose, managing manual navigation and initiating slider dragging. Here’s the code:

function dragStart(e) {
    clearInterval(interval);
    interval = null;

    // handling manual navigation
    if (navigation.contains(e.target)) {
      counter = e.target.id ? parseInt(e.target.id) - 1 : counter;
      slidesFlex.style.transition = "0.8s";
      moveSlide();
      createInterval();
      return;
    }
//initiating drag action
    e.preventDefault(); // for touchscreen defaults

    // sliding animation
    slidesFlex.style.transition = "0.5s";
    container.style.cursor = "grabbing";
    document.body.style.cursor = "grabbing";

    clicked = true;

    if (e.type == "touchstart") {
      initialX = e.touches[0].clientX;
    } else {
      initialX = e.clientX;
      document.onmousemove = dragging;
      document.onmouseup = dragStop;
    }
  }
Enter fullscreen mode Exit fullscreen mode

The function accomplishes the following:

  1. Clearing Autoplay Interval: The function starts by clearing any existing interval and setting it to null. This action prevents conflicts between the autoplay and user-initiated dragging.
  2. Manual Navigation Handling: When a user interacts with the navigation elements, the function:
    1. Updates counter to the id of the clicked label element, if available.
    2. Adjusts the transition timing of slidesFlexto 0.8 seconds for a smoother transition effect.
    3. Calls the moveSlide() function to shift the slides to the selected position.
    4. Calls createInterval() to resume autoplay.
  3. Dragging Functionality Handling: In the case of dragging, the function:
    1. Prevents default touch events to ensure the slider functions as intended on touchscreen devices.
    2. Adjusts the transition timing to 0.5 seconds for a more responsive sliding effect.
    3. Changes the cursor style of the container and the document to “grabbing” to indicate a dragging action.
    4. Updates clicked to true, signifying the user’s intent to drag the slider.
    5. Updates the initialX to the current horizontal position of the user (clientX). On touchscreen devices, it first accesses the touches property, an array of touch points, and uses touches[0] for the first touch point on the element.
    6. Declares event listeners for “mousemove” and “mouseup” events on document, enabling the dragging action.

As shown below, clicking the navigation buttons both shifts the slides to your desired position and resets the interval timer. Conversely, clicking the slider itself readies it for dragging and temporarily pauses the interval:

our slider with the dragStart function

dragging()

This callback function updates the position of slideFlex as the user drags it within the container. Here’s the code:

function dragging(e) {
    if (!clicked) return;

    if (e.type == "touchmove") {
      finalX = e.touches[0].clientX;
    } else {
      finalX = e.clientX;
    }

    let currentPosition = counter * leftPos;

    slideDistance = ((initialX - finalX) / (slidesWidth / numberOfSlides)) * 100;

    if (Math.abs(slideDistance) < threshold) {
      slidesFlex.style.left = `${currentPosition - slideDistance}%`;
    }
  }
Enter fullscreen mode Exit fullscreen mode

The function:

  1. Checks if there was a prior “mousedown” or “touchstart” event before allowing dragging.
  2. Updates the value of finalX to the user’s final horizontal position.
  3. Calculates the current position of slidesFlex.
  4. Determines the slide distance as a percentage of the width of each slide.
  5. Ensures the slide distance is within the specified threshold before subtracting it from the current position.

Here, we’ve successfully implemented the dragging motion based on the pointer’s direction. However, this action does not yet trigger a slide switch:

our slider with the dragging function

This callback function handles slide switching when the user stops dragging. Here’s the code:

function dragStop(e) {
    if (navigation.contains(e.target)) return;

    // Check threshold and counter before changing slides
    if (finalX < initialX && counter < (numberOfSlides - 1) && slideDistance >= threshold) {
      counter++;
    } else if (
      finalX > initialX &&
      counter > 0 &&
      -slideDistance >= threshold
    ) {
      counter--;
    }
    moveSlide();

    // Reset to default
    createInterval();
    document.body.style.cursor = "default";
    container.style.cursor = "grab";
    initialX = undefined;
    finalX = undefined;
    clicked = false;
    document.onmousemove = null;
    document.onmouseup = null;
  }
Enter fullscreen mode Exit fullscreen mode

The function:

  1. Ensures the user intends to drag rather than use manual navigation.
  2. Checks the distance and direction of the user’s drag before updating counter accordingly.
  3. Calls moveSlide() to switch slides to the current counter value.
  4. Calls createInterval() to resume autoplay.
  5. Resets all other variables and cursor styles in preparation for the next dragging action.
  6. Sets document event listeners to null to prevent unnecessary resource usage.

As shown below, the callback functions (dragStart to dragEnd) combine to create a smooth dragging experience:

a draggable slider

Finally, to prevent redundancy, let’s comment out the following sections in our CSS:

  • The transition timing on .slide-flex
  • The repositioning of .slide-flex during the checked state of the radio inputs

This removal is necessary because inline styles applied via JavaScript take precedence over external CSS.


.slides-flex{
...
/* transition: 0.8s */
}
...

/* ON RADIO CHECKED */
/* #radio1:checked ~ .slides-flex {
  left: 0%;
}

#radio2:checked ~ .slides-flex {
  left: -100%;
}

#radio3:checked ~ .slides-flex {
  left: -200%;
}

#radio4:checked ~ .slides-flex {
  left: -300%;
} */
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this tutorial, we’ve explored the fundamental elements needed to create a draggable slider using nothing but HTML, CSS, and JavaScript. Equipped with this knowledge, you’re ready to enhance user interactivity and improve the user experience in your projects.

If you found this article enlightening or have any thoughts to share, please give it a like and leave your comments below.

Some Helpful Links

Explore further:

💖 💪 🙅 🚩
lynxdev32
Ayobami Ajayi

Posted on October 17, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related