Building a Slider

Beginner JavaScript

Enjoy these notes? Want to Slam Dunk JavaScript?

These are notes based on my Beginner JavaScript Video Course. It's a fun, exercise heavy approach to learning Modern JavaScript from scratch.

Use the code BEGINNERJS for an extra $10 off.

BeginnerJavaScript.com

In this video, we will be building a slider.

building a slider

A slider may seem simple, but once you dive into it, it starts to get pretty complex. We will build the basics of the slider together and then you can feel free to add any functionality you want to it.

Similarly to the last lesson, we will be writing everything in the same JavaScript file, and then later we will revisit this exercise and refactor it twice to use prototypes and classes.

We have 2 sliders on the page, so we can be sure that our code can be used more than once on the same page.

If we look at the HTML structure of the slider within the element tab of the dev tools, you will see that we have all 20 slides. Now as we hit the next button and previous buttons, the classes are actually changing on the sliders depending on what button you press.

changing slides on the click on previous or next button

In this lesson we will use SASS, just to demonstrate that it is possible. Wes will show us how to get that up and running.

We are starting with the CSS shown below.

.slide {
  position: absolute;
  background: var(--pink);
  height: 100%;
  width: 100%;
  display: grid;
  align-content: center;
  justify-content: center;
  color: white;
  font-size: 100px;
  font-family: sans-serif;
  border: 5px solid white;
  transition: all .25s;
  transform: translateX(-200%);
}

The slide class positions the slide absolutely, using display: grid. Then we take the slides and put them off the screen like so 👇

.slide.prev {
  z-index: 10;
  transform: translateX(-100%);
}

.slide.current {
  z-index: 10;
  transform: translateX(0);
}

.slide.next {
  z-index: 10;
  transform: translateX(100%);
}

Our current slide has translateX(0), which puts it in the middle. As we modify the value that we pass to translateX, you will see the slide is moving.

translating a slide on X-axis

By translating something 100%, we are essentially just putting it outside the slides div and we have an overflow:hidden on it so you cannot see what is outside of it.

Have the previous, current and next slide on translateX -100, 0 and then 100% makes for that animation. Wes has added a transition on the animation to make it look smooth.

Back to the SASS, in the SASS file we aren't actually using any special SASS syntax, so you could change the file extension to .css and it would work. However, Wes wanted to demonstrate that the Parcel bundler is able to recognize a SASS file if you add it via a stylesheet tag.

parcel bundler recognize sass files

The Parcel bundler detects that the file is a .scss extension and it will compile it SASS for us before it puts it into the browser.

Let's get started. Navigate in our terminal to the exercises/59 - Slider folder. Run npm install once you are in that directory.

While that installs, open up the package.json file and take a look.

package.json file

You will notice that we have 2 dev dependencies, one for Sass and one for Parcel. When we type npm start, that will run the parcel index.html command.

adding start script in package.json

You should see the server is running on a specific port indicated in your terminal. Nothing should be happening on the page because haven't added any JavaScript yet.

Our JavaScript file lives in the src directory of our current exercise folder. Putting scripts in a src or lib folder is a pretty common thing to do. There is nothing special there, it's just a way to organize your files.

Let's start writing some code. Create a function called Slider which will take in an argument, slider which will be the reference to the slider element that was passed as an argument.

(Sometimes you will see people naming elements with an "El" at the end, just to specify that it is an element, but we won't do that.)

Let's create 2 variables, one for each of the sliders we have on the page.

function Slider(slider) {

}
const mySlider = Slider(document.querySelector('.slider'));
const dogSlider = Slider(document.querySelector('.dog-slider'));

Within the slider function, add a check for whether someone has passed a slider in or not.

if (!slider) {
  throw new Error('No slider passed in');
}

Now if you were to run the function and pass in anything instead of a reference to an element like const mySlider2 = Slider(12322), it would not error out, because we are just checking whether we are getting anything.

How can we check whether the function was passed in an actual HTML element? Like Wes has mentioned before, if we look at the docs for document.querySelector(), you will see that it returns an element.

The element is the most general base class from which all objects in a document inherit. That may not be very interesting to you, but wh at that means is when we create a div or a span, it inherits all of it's base attributes from the Element in the browser.

If you open up your dev tools and type Element, you will see that it is a function.

typing element in console

This means that we can use document instanceof Element to check whether something is an instance of the Element base class.

889

Modify the code as shown below 👇

if (!(slider instanceof Element)) {
  throw new Error('No slider passed in');
}

As we move through the slider, we will need to keep track of what is the current, next and previous slides. Create some empty variables that we will use to keep track of that.

let current;
let prev;
let next;

We will set those values when the slider starts, and when the user navigates with the previous and next buttons.

Next, we need to select the elements needed for a our slider. If you look at the HTML code, it consists of all the slides and then the next and previous buttons.

const slides = slider.querySelector('.slider');
const prevButton = document.querySelector('.goToPrev');
const nextButton = document.querySelector('.goToNext')

Just like we did in the last lesson, we will be adding a whole bunch of functions to add and remove CSS classes and figure out what is next.

Create a function called startSlider. Within that function we will be populating those variables.

creation of startSlider function

We want to update the current variable from within this function. The reason we are creating the variable outside of the function is that it needs to be accessible by other functions we will create in the future like move next functionality.

If we created the variable inside of the startSlider function, that would make it scoped only to the function, and it would not be accessible elsewhere. By creating the variable at the top, all of the functions that live inside of our slider will have access to it.

That is the concept of what a closure is. We have these variables that existing within our Slider function that other functions will be able to grab onto. They are not global variables, they are variables that live inside the closure of the slider function.

We will set the current variable to be either the first slide that is in the slider, or where the slider has the class of current (so you could set the slider to start at a specific image on page load).

In one of the sliders on our HTML page, Wes has set the starting slide by passing a class of current to one of the slides and in the second slide there is no current class on load.

We will set the value of current to be the slide that contains the class of "current" or if no slider has a class of current, we will get the first element child of slides.

function startSlider() {
  current = slider.querySelector('.current') || slides.firstElementChild;
  console.log(current);
}
setting the class current

When we create the slider, we now have to run the startSlider function. Add it to the bottom of the Slider function.

//when this slider is created, run the start slider function
startSlider();

That is often what we refer to as a constructor, which we will be getting into more when we hit classes.

If you refresh the page you will see that we now have both of our slides logged in the console. The first slide starts at 16 because it has the class current on it and the slider had no class of current on any of the slides so it started on the first slide.

current slide in console

For the previous one we will grab the element that is behind the current one using previousElementSibling.

Let's demo how that works by going to the HTML page in the browser, selecting slide #2 in the elements tab and then going back to the console and typing $0.previousElementSibling. You will see that the slide that says 1 will be returned.

selecting the slide from elements tab

If you were to do the same thing but with #1 slide, you would get null because because there is nothing next to one.

getting null on selecting the first slide

So we want to set the previous slide to be the previousElementSibling, or if that doesn't exist, we can fall back to the last slide within our slides div.

prev = current.previousElementSibling || slides.lastElementChild;

Why is Wes using the Element version of the methods instead of using current.previousSibling or slides.lastChild? What is the difference between lastElementChild and lastChild?

Let's demonstrate with an example.

Add a paragraph at the top of the HTML page such as <p>I <strong>love</strong> to eat <strong>pizza</strong></p> and inspect it.

adding a paragraph on the top of html page

Click on "love", and then go into the console and try the following 👇

$0
$0.nextSibling;
$0.nextElementSibling;
executing $0, $0.nextSibling, $0.nextElementSibling after clicking on element

What is the difference? nextSibling gives us a Node, and a node can be text or an element, whereas nextElementSibling will only return elements. Always use elementSibling when you are looking for an element.

Next will be next = current.nextElementSibling || slides.firstElementChild;. Then we will add console.log({current, prev, next });

function startSlider() {
  current = slider.querySelector(".current") || slides.firstElementChild;
  prev = current.previousElementSibling || slides.lastElementChild;
  next = current.nextElementSibling || slides.firstElementChild;
  console.log({ current, prev, next });
}
startSlider function definition

If you open the console, you will see we have current, prev, next.

Now we need to start applying classes to those to make them show up.

Make a function applyClasses and we will take the current, add the class of current to it and then do the same the prev and next.

function applyClasses() {
  current.classList.add('current');
  prev.classList.add('prev');
  next.classList.add('next');
}

Now when the code runs, we will just run applyClasses as well.

startSlider();
applyClasses();

If you were to inspect the code you would see that now the appropriate slides have classes.

appropriate slides have class current

Next we need a method called move that takes in a direction like back or forwards.

function move(direction) {

}

When we call the move function it will move the classes around to switch which the current, next and previous slides are. You could manually edit the classes yourself in the element panel to do that.

The first thing this function will do is strip all the current classes off the slides.

Make an array of classes to remove and put the classes that we want to remove inside of the array. Then take the previous element and call remove(). Remove will take in as many arguments of classes that you want to remove as you want.

We will have to do that with current and next as well which is kind of annoying.

prev.classList.remove("prev", "current", "next");
current.classList.remove("prev", "current", "next");
next.classList.remove("prev", "current", "next");

What is even better is we can take the array of classes to remove and spread it into the remove method, like so 👇

function move(direction) {
  // first strip all classes off the current slides
  const classesToRemove = ['prev', 'current', 'next'];
  prev.classList.remove(...classesToRemove);
  current.classList.remove(...classesToRemove);
  next.classList.remove(...classesToRemove);
}

We could even make that shorter by putting our elements into an array and then calling forEach on them and then within the forEach calling remove and spreading the classesToRemove array into it.

[prev, current, next].forEach(el => el.classList.remove(...classesToRemove));

Let's go with the approach where we have three lines instead of the one liner to make it more readable instead however.

Now we need to figure out which direction the slides are going.

If the slides are going backwards, the need to take our current, previous and next variables and shift them by one. If we are going backwards previous will become current, current will become next. We are basically shifting everything to the left.

There is a bit of a problem when you are assigning variabels to be each other. Let's say we tried the code below 👇

if (direction === 'back') {
  prev = prev.previousElementSibling;
  current = prev;
}

We have run into this problem where we have already updated what previous is. How do we access the old previous? We could do have a variable called oldPrev and then assign the value of prev to that before we reassign it but that is not the best way.

What Wes will show us instead is how to use destructuring to switch variables easily.

We will destructure the [prev, current, next] variables and make an array of their new values and destructure them over and into the prev, current and next variables.

That means the first thing we put into the array will be assigned to prev. Then next item will be current and the last will be next.

The first thing will be prev.previousElementSibling. Then the current will be the prev and the next will become the current.

[prev, current, next] = [prev.previousElementSibling, prev, current];

Now we will do the opposite if the direction is forward.

if (direction === "back") {
  [prev, current, next] = [prev.previousElementSibling, prev, current];
} else {
  [prev, current, next] = [current, next, next.nextElementSibling];
}

That seems confusing but all we are doing is shifting them all one lower.

Next we will just run the applyClasses() function which will in turn add the current, previous and next classes.

Now we can take our previous button and our next button and hook up click events.

Let's do it at the very bottom of our Slider function.

Call the move function from the event listener handlers and pass 'back' to when the previous button is clicked using an arrow function. When the next button is clicked, we will just pass reference to move since unless you pass the string "back", it will move in the forward direction.

// Event Listeners

prevButton.addEventListener('click', ()=> move('back'));
nextButton.addEventListener('click', move);

While handling an event, if you need to pass an argument to the event-listener, you need to use an arrow function here. Or, you use call and apply which you will learn about in the next video.

Now when you click it, you will see the slider works.

One error is when you get to the very end or very beginning, we will get an arrow because we lose our next class.

prev and current class

Now we need to say when we have the edge case where a previous element or next sibling doesn't exist you will add an or condition and get the slides.lastElementChild or slides.firstElementChild for the next button.

if (direction === "back") {
  [prev, current, next] = [
    // get the prev slide, if there is none, get the last slide from the entire slider for wrapping
    prev.previousElementSibling || slides.lastElementChild,
    prev,
    current,
  ];
} else {
  [prev, current, next] = [
    current,
    next,
    // get the next slide or if its at the end, loop around and grab the first
    next.nextElementSibling || slides.firstElementChild,
  ];
}

Now if you refresh the page you will see the slider is working but the second one is not. Why not? Let's debug.

The reason is that the buttons are not working for the second slider.

Why is that?

Because if you look back at where we grab the buttons in our code, we are using document.querySelector() instead of querySelectorAll() to grab the buttons, which means both sliders are being moved by the same button. querySelector just finds the first element on the page and binds to that. It shouldn't be document, it should be slider because we need to look for the buttons within the slider itself.

If you refresh the page you will see they now run on their own.

slides running on their own

63 lines of code for a slider!

It would be cool to get the arrow keys working but only when someone is focused in on one of the dev. That would be an interesting exercise to give a shot yourself if you're interested.

We will be revisiting this exercise in our prototype lesson. The functions, move, applyClasses and startSlider are going to be moving to what are called the prototype.

Find an issue with this post? Think you could clarify, update or add something?

All my posts are available to edit on Github. Any fix, little or small, is appreciated!

Edit on Github

Syntax Podcast

Hold on — I'm grabbin' the last one.

Listen Now →
Syntax Podcast

@wesbos Instant Grams

Beginner JavaScript

Beginner JavaScript

A fun, exercise heavy approach to learning Modern JavaScript from scratch. This is a course for absolute beginners or anyone looking to brush up on their fundamentals. Start here if you are new to JS or programming in general!

BeginnerJavaScript.com
I post videos on and code on

Wes Bos © 1999 — 2024

Terms × Privacy Policy