DEV Community

Cover image for Vanilla JavaScript: How To Create a Draggable Slider With Auto-Play and Navigation.
Ayobami Ajayi
Ayobami Ajayi

Posted on • Originally published at lynxdev.hashnode.dev

Vanilla JavaScript: How To Create a Draggable Slider With Auto-Play and Navigation.

Sliders, also known as carousels, are a popular tool for modern websites. They enable efficient transmission of information within a limited space.

Developers often rely on frameworks to build sliders. Although productive, this method does not contribute much to your growth as a developer.

Therefore, in this tutorial, we’ll adopt a more traditional approach to this project by using plain HTML, CSS, and JavaScript.

Codepen here:

Warming up

Before proceeding, I want to introduce two fundamental concepts that this project relies on.

Firstly, when you link a label element to an input element, like this:

<input type="radio" id="radio1"/>
<label for="radio1">label for radio1</label>
Enter fullscreen mode Exit fullscreen mode

Clicking that label on the webpage passes the focus to the linked input element, as shown below:

a linked label

This behavior is consistent even when you reposition either of the elements using CSS:

label{
   position: fixed;
   top: 10rem;
   right: 5rem;
}
Enter fullscreen mode Exit fullscreen mode

The output:

Furthermore, you can apply this concept to other input types, such as checkboxes, numbers, and text fields. This linking expands the clickable area of the input element, and, as you will discover later, it allows for some neat tricks.

Secondly, using CSS pseudo-classes and combinators, you can dynamically change the styling of an element based on the current state of another element.

Pseudo-classes allow you to style specific states of elements. For example, :hover targets an element’s state when you hover over it with your mouse.

p{
  color: black;
}

p:hover{
  color: red;
}
Enter fullscreen mode Exit fullscreen mode

Note that all pseudo-classes begin with a single colon (:).

Several pseudo-classes in CSS allow you to target various states of elements, but I’ll draw your attention to the :checked pseudo-class in this tutorial.

:checked is used to target the state when you toggle on a radio input or checkbox. For example, let’s consider our previous input element:

input:checked{
  outline: 2px solid red;
  outline-offset: 10px;
}
Enter fullscreen mode Exit fullscreen mode

This output:

You can see that the browser applied the styles only after you toggled the radio on.

Alternatively, combinators establish the connection between selectors. There are four types of combinators in CSS:

  • General sibling combinator (~)
  • Adjacent sibling combinator (+)
  • Child combinator (>)
  • Descendant combinator (space)

Here, we’ll focus on the general sibling combinator. The general sibling combinator allows you to style sibling elements in your HTML.

For example, consider the following CSS rule:

.child1 ~ .child2{
  /* some declarations */
}
Enter fullscreen mode Exit fullscreen mode

This selector initially identifies any element in our HTML with a class of .child1. It then applies the specified styles to any of its younger siblings with a class of .child2, as demonstrated below:

You should note the following about any two elements connected in this way:

  • They need to be sibling elements, which means they must share a common parent element, as shown above.
  • They do not need to be immediate siblings. This combinator disregards any other siblings placed between them.
  • Finally, you cannot apply combinator styles upwards in the HTML tree, only downwards. Thus, .child2 must come after .child1, making .child2 a younger sibling.

Putting it all together, and still on our input and label elements from before, we have:

input:checked ~ label {
  outline: 2px solid red;
  outline-offset: 10px;
}
Enter fullscreen mode Exit fullscreen mode

The output:

As you can see, we dynamically changed the styling of a sibling element(label) based on the :checked state of the input element.

With all that out of the way, we can continue with our program.

The HTML

Our first task is to create a section of class slider within the body element.

<body>
  <section class="slider"></section>
</body>
Enter fullscreen mode Exit fullscreen mode

Within this section, we’ll add:

  1. Four radio inputs, one for each slide. Each radio input should have a unique id.
  2. An unordered list (ul) with class slides-flex. It should contain four list elements (li), each assigned a class slide. These li will serve as our individual slides, containing whatever we wish to to display.
  3. A div with class navigation. This should contain four label elements, each linked to one of the four radio inputs previously mentioned. Each label element should also have a unique class and an id between 1 and 4, corresponding to its position in the parent element.
<body>
    <section class="slider">
      <!-- radio buttons start -->
      <input type="radio" name="radio-btn" id="radio1" />
      <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

Our HTML section is now complete. You can copy this into your editor.

As you can see, our webpage is starting to take shape:

The CSS

You can adapt most of the styling of this project to your specific use case. However, in this section, I’ll explain the fundamental style decisions that make this project work.

Global Styling

Firstly, let’s remove the default margins, paddings, and box-sizing present on each element using the global selector (*). This improves control over element styling and reduces variation across browsers.

*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
Enter fullscreen mode Exit fullscreen mode

Next, the :root selector allows you to create custom variables that can reused throughout your style sheet. We’ll use color: #333; numerous times in our style sheet, and creating a custom variable is essential to avoid repetition.

:root{
 --primaryColor: #333;
 }
Enter fullscreen mode Exit fullscreen mode

Note that we can later access this custom variable using color: var(--primaryColor);.

Now, for our body element:

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

ul {
  list-style: none;
}
Enter fullscreen mode Exit fullscreen mode

Here, we set the following properties:

  • background-color: To provide a consistent visual backdrop for our webpage.
  • color and font-family: We apply these styles globally to all child elements.
  • display: grid; and place-items: center;: When used together, they center all child elements. It’s important to note that the parent element requires a specified height property for this method to work. Our height in this case, spans the entire viewport (100vh).
  • list-style: none;: To remove the bullets from all list elements on our webpage.

Our webpage should look like this:

Class .slider

This will serve as a container to showcase our slides.

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

.slider input {
  display: none;
}
Enter fullscreen mode Exit fullscreen mode
  • position: relative;: This accommodates the later placement of absolutely positioned elements.
  • aspect-ratio: 1;: Ensures the element remains square with equal sides, eliminating the need to set the height property for improved responsiveness.
  • max-height and max-width: Set limits on how large the element can become on larger screen sizes.
  • overflow: hidden;: We will discuss more on this later.
  • cursor: grab;: Indicates that the element is draggable.
  • display: none;: Hides the element from the viewport. Note that we can still access the radio inputs programmatically even though they are invisible.

The output:

Note that the borders are only present for visualization purposes, and we’ll remove them later.

Class .slides-flex

In this section, we’ll arrange the slides horizontally using Flexbox.

Moreover, this section serves as the dynamic part of our project. To achieve this, it needs to have absolute positioning so we can adjust it relative to its relatively positioned parent(.slider).

.slides-flex {
  display: flex;
  width: 400%;
  height: 100%;
  position: absolute;
  left: 0;
  transition: 0.8s;
}
Enter fullscreen mode Exit fullscreen mode
  • width: 400%;: This sets the width to four times that of its parent.
  • height: 100%;: Stretches the element to fill the entire height of its parent.
  • position: absolute;: Enables us to use the left: 0; declaration relative to its parent, placing it at the left edge of .slider.
  • transition: 0.8s;: Used to create a smooth motion and introduce a sliding effect.

Class .slide

In this section, we’ll style each of our slides. We’ll start with the general layout by styling all elements with a class of .slide. We’ll then target each slide using the :nth-child pseudo-class.

.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;
  height: 5rem;
  width: 5rem;
  font-size: 3rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  text-align: center;
  outline: 3px solid;
}
Enter fullscreen mode Exit fullscreen mode
  • border-radius: This property can accept one to four values. When using four values, they are applied clockwise starting from the top-left corner. In our first slide (0.7rem 0 0 0.7rem), we aim to round the top-left and bottom-left corners. Conversely, for our fourth slide (0 0.7rem 0.7rem 0), we intend to round the top-right and bottom-right corners. You’ll see the importance of this in the JavaScript part.
  • user-select: none;: Prevents unnecessary text highlighting while dragging each slide with the mouse.

The output:

Class .navigation

For our navigation, let’s style each label to look like radio inputs.

navigation {
  position: absolute;
  display: flex;
  width: 100%;
  bottom: 2.5rem;
  gap: 2rem;
  justify-content: center;
}

.navigation label {
  border: 2px solid var(--primaryColor);
  padding: 0.3rem;
  border-radius: 50%;
  cursor: pointer;
  transition: 1s;
}
Enter fullscreen mode Exit fullscreen mode
  • position: absolute;: Enables the use of bottom: 2.5rem;, positioning it 2.5rem from the bottom of .slider.
  • cursor: pointer;: Indicates to the user that the element is clickable.
  • border-radius: 50%;: Transforms each label into a circle.

Here’s what the navigation should look like:

On :checked

As mentioned earlier, the :checked pseudo-class allows access to the checked state of radio inputs. And when used with the general sibling combinator (~), grants access to their younger siblings:

  1. Class .slides-flex , and
  2. Class .navigation.

Starting with .slides-flex:

#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

In CSS, when you use a percentage value to position an element, that value is relative to the width of its parent element. So, when you set left: -100%;, it shifts the element to the left, outside its parent element, at a distance equal to 100% of its parent’s width, as demonstrated below:

left: -100%;

As a reminder, we previously defined the width of .slides-flex to be four times(400%) that of .slider. Therefore, setting left: -100%; simply shifts .slides-flex by one-quarter of its width to the left, bringing a new slide to focus.

Looking at the image below, clicking each button after the first subtracts an additional 100% from the position of.slides-flex, bringing the slide at that position into focus.

Now for .navigation, so we can know what slide we’re in:

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

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

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

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

Notice how, first, we selected the parent element of the buttons, which is a sibling to the radio inputs. Then, we accessed each button using the descendant combinator (space).

Now, when we click any button, it changes its background color then brings a corresponding slide into focus, as shown below:

Rounding up, to ensure that only the selected slide is visible, let’s uncomment the line overflow: hidden; in our .slider styling. This change hides content outside the boundaries of the parent element, which, in this case, includes all other slides.

We can also comment out the border styling while at it:

.slider{
  ...
  overflow: hidden;
  ...
  /* border: 2px solid green; */
}
Enter fullscreen mode Exit fullscreen mode

The output:

Finally, if you look at the image above, you’ll see that upon reload, no label is checked, and users won’t know what slide they are currently on. To avoid this, let’s add the checked attribute to our #radio1. This checks the first radio input on every reload, indicating that we are currently on the first slide.

<input type="radio" name="radio-btn" id="radio1" checked />
Enter fullscreen mode Exit fullscreen mode

The output:

That concludes the CSS part. So far, we’ve implemented the sliding animations and manual navigation. Regrettably, for the sake of brevity, we must pause here. I’ll release a sequel to this article covering the JavaScript part, allowing us to achieve the:

  1. Autoplay, and
  2. Dragging (mouse) or swiping (touchscreen) options. Stay tuned!

Conclusion

In this tutorial, we’ve learned that not all webpage functionality needs to rely solely on JavaScript. By utilizing CSS pseudo-classes and combinators, we dynamically modified our webpage elements—a task that might otherwise have cost us several lines of JavaScript code.

Finally, if you’ve learned anything new, consider dropping a like and sharing so others can benefit as well. Until next time.

Some Helpful Links

Find out more on:

  • CSS pseudo-classes here
  • CSS combinators here

Top comments (4)

Collapse
 
artydev profile image
artydev • Edited

Thanks

Collapse
 
lynxdev32 profile image
Ayobami Ajayi

You’re welcome✨

Collapse
 
rouilj profile image
John P. Rouillard

This looks neat. Will your next article also cover accessibility?

Collapse
 
lynxdev32 profile image
Ayobami Ajayi

Thanks a lot!
I understand the place of accessibility in projects like these. However, this tutorial aimed to keep things as stripped-down as possible, allowing for future modification. While adhering to front-end best practices, my primary focus was on clarity.

Nevertheless, I plan to write additional articles addressing accessibility in more detail.