🛠 Carousel: Working the dots

Hey ,

I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.

Please upgrade (use this link) access this content.

I'm super eager to help you learn more!

🛠 Carousel: Working the dots

When a user clicks on a dot, the carousel shows the slide that corresponds to the clicked dot.

  • Click first dot: Show first slide
  • Click second dot: Show second slide
  • Click third dot: Show third slide
Completed dot interaction.

First, we need to get the dots in JavaScript.

const dots = Array.from(carousel.querySelectorAll('.carousel__dot'))

We want to know when a dot gets clicked. To do this, we need to add an event listener to each dot.

dots.forEach(dot => {
  dot.addEventListener('click', event => {
    console.log(dot)
  })
})
Listening to the clicked dot.

When a dot gets clicked, we want to find the corresponding slide.

  • First dot: First slide
  • Second dot: Second slide
  • Third dot: Third slide

To do this we need to know the position of the dot that was clicked. (Did the user clicked the first dot, second dot, or third dot?)

If the user clicks the first dot, dots[0] and dot should be the same thing. We can compare them with the strictly equal operator ===.

dots.forEach(dot => {
  dot.addEventListener('click', event => {
    if (dots[0] === dot) {
      console.log('Clicked first dot')
    } else {
      console.log('Clicked another dot')
    }
  })
})

The same applies for other dots:

  1. If user clicks second dot, dots[1] === dot
  2. If user clicks third dot, dots[2] === dot

We can loop through dots and check which dot was clicked.

dots.forEach(dot => {
  dot.addEventListener('click', event => {
    let clickedDotIndex

    for (let index = 0; index < dots.length; index++) {
      if (dots[index] === dot) {
        clickedDotIndex = index
      }
    }

    console.log(clickedDotIndex)
  })
})
Getting position of the dot that was clicked.

Once we know clickedDotIndex, we can use it to find the slide to show.

const slides = Array.from(carousel.querySelectorAll('.carousel__slide'))

dots.forEach(dot => {
  dot.addEventListener('click', event => {
    let clickedDotIndex

    for (let index = 0; index < dots.length; index++) {
      if (dots[index] === dot) {
        clickedDotIndex = index
      }
    }

    const slideToShow = slides[clickedDotIndex]
    console.log(slideToShow)
  })
})
Getting the slide to show.

Once we know the slide to show, we can get its left position with getComputedStyle.

dots.forEach(dot => {
  dot.addEventListener('click', event => {
    // ...
    const slideToShow = slides[clickedDotIndex]
    const destination = getComputedStyle(slideToShow).left
    console.log(destination)
  })
})

And we can show the slide by changing .carousel__content's left position.

dots.forEach(dot => {
  dot.addEventListener('click', event => {
    // ...
    const slideToShow = slides[clickedDotIndex]
    const destination = getComputedStyle(slideToShow).left

    contents.style.left = '-' + destination
  })
})
Showing slides with dots

After changing the selected slide, we need to update the location of the is-selected class. (Without this, our previous and next buttons will not work).

To do this, we need to remove the is-selected class from the current slide. We can find the selected slide with querySelector

dots.forEach(dot => {
  dot.addEventListener('click', event => {
    // ...
    const currentSlide = contents.querySelector('.is-selected')
    currentSlide.classList.remove('is-selected')
  })
})

An alternate way is to loop through all slides and remove the is-selected class from them all.

const slides = Array.from(carousel.querySelectorAll('.carousel__slide'))

dots.forEach(dot => {
  dot.addEventListener('click', event => {
    // ...
    // Either use this code or the one above. Pick one!
    slides.forEach(slide => {
      slide.classList.remove('is-selected')
    })
  })
})

Then, we add is-selected to the newly displayed slide.

dots.forEach(dot => {
  dot.addEventListener('click', event => {
    // ...
    slides.forEach(slide => {
      slide.classList.remove('is-selected')
    })
    slideToShow.classList.add('is-selected')
  })
})

Updating dot state

Dots on a carousel help to indicate the selected slide. The selected dot should be styled differently from the rest of the dots. We can style the selected dot with an is-selected class.

<div class="carousel__dots">
  <button class="carousel__dot is-selected"></button>
  <button class="carousel__dot"></button>
  <button class="carousel__dot"></button>
</div>
Styled the first dot.

To update the styled dot, we remove the is-selected class from all other dots. And we add is-selected back to the clicked dot.

dots.forEach(dot => {
  dot.addEventListener('click', event => {
    // ...
    dots.forEach(d => {
      d.classList.remove('is-selected')
    })
    dot.classList.add('is-selected')
  })
})
Styles the dot that was clicked.

Updating dot state when clicking buttons

Dots should also get updated when a user clicks the previous and next buttons.

When the user clicks the next button, we need to find the selected dot with querySelector. Then, we find the next dot with nextElementSibling. Then, we remove and add the is-selected class accordingly.

const dotsContainer = carousel.querySelector('.carousel__dots')

nextButton.addEventListener('click', event => {
  // ...
  // Highlight dot
  const currentDot = dotsContainer.querySelector('.is-selected')
  const nextDot = currentDot.nextElementSibling
  currentDot.classList.remove('is-selected')
  nextDot.classList.add('is-selected')
})

We can repeat the same steps for the previous button listener.

previousButton.addEventListener('click', event => {
  // ...
  // Highlight dot
  const currentDot = dotsContainer.querySelector('.is-selected')
  const previousDot = currentDot.previousElementSibling
  currentDot.classList.remove('is-selected')
  previousDot.classList.add('is-selected')
})
Highlights the correct dot when clicking on the previous or next button.

Showing/hiding previous and next buttons

There’s one more thing we need to do:

  • If the user clicks on the first dot, we need to hide the previous button, but show the next button.
  • If the user clicks the second dot, we need to show both the previous and next buttons.
  • If the user clicks the third dot, we need to show the previous button, but hide the next button.

Let’s work it out step by step.

Clicking the first dot

When a user clicks the first dot, we want to show the next button, but hide the previous button. We can use clickedDotIndex to check if they’re clicking on the first dot.

We know these to be true:

  1. If clickedDotIndex is 0, they click the first dot
  2. If clickedDotIndex is 1, they click the second dot
  3. If clickedDotIndex is 2, they click the third dot
dots.forEach(dot => {
  dot.addEventListener('click', event => {
    // ...

    // Show / hide buttons
    if (clickedDotIndex === 0) {
      previousButton.setAttribute('hidden', true)
      nextButton.removeAttribute('hidden')
    }
  })
})
Clicks first dot. Hides previous button, shows next button.

Clicking the last dot

If the user clicks on the last dot, we want to show the previous button, but hide the next button. We know they clicked on the last dot if clickedDotIndex is 2.

dots.forEach(dot => {
  dot.addEventListener('click', event => {
    // ...

    // Show / hide buttons
    if (clickedDotIndex === 0) {
      previousButton.setAttribute('hidden', true)
      nextButton.removeAttribute('hidden')
    } else if (clickedDotIndex === 2) {
      previousButton.removeAttribute('hidden')
      nextButton.setAttribute('hidden', true)
    }
  })
})
Clicks last dot. Shows previous button, hides next button.

This code works if there are three dots. We need to make the code work for 2 dots, 4 dots, and any other number of dots.

Here, we know this:

  1. If last dot is 2, there are 3 dots in total
  2. If last dot is 3, there are 4 dots in total
  3. If last dot is 4, there are 5 dots in total

We can get the index of the last dot once we know the total number of dots. We can get the total number of dots with the length property.

dots.forEach(dot => {
  dot.addEventListener('click', event => {
    // ...

    // Show / hide buttons
    if (clickedDotIndex === 0) {
      previousButton.setAttribute('hidden', true)
      nextButton.removeAttribute('hidden')
    } else if (clickedDotIndex === dots.length - 1) {
      previousButton.removeAttribute('hidden')
      nextButton.setAttribute('hidden', true)
    }
  })
})

Clicking the second dot

If the user clicks the second dot (or any dots between the first and last dot), we want to show both arrows. We can do this with an else statement.

dots.forEach(dot => {
  dot.addEventListener('click', event => {
    // ...

    // Show / hide buttons
    if (clickedDotIndex === 0) {
      previousButton.setAttribute('hidden', true)
      nextButton.removeAttribute('hidden')
    } else if (clickedDotIndex === dots.length - 1) {
      previousButton.removeAttribute('hidden')
      nextButton.setAttribute('hidden', true)
    } else {
      previousButton.removeAttribute('hidden')
      nextButton.removeAttribute('hidden')
    }
  })
})
Clicks the middle dot. Shows both previous and next buttons.

That’s it!