How to Create a JavaScript Slider with a Beautiful Timer using Greensock

How to Create a JavaScript Slider with a Beautiful Timer using Greensock

Let’s build the slider controls as on the Museum of Science and Industry of Chicago’s website.

Photo by [Veri Ivanova](https://cdn.hashnode.com/res/hashnode/image/upload/v1621430502036/TGQdeGRyQ.html) on [Unsplash](https://unsplash.com/s/photos/timer?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)Photo by Veri Ivanova on Unsplash

The website I just mentioned in the subtitle of this article is pretty nice. If you land on the homepage, you will see the background images that change and slide up after a few seconds. On the left side, there’s a typical JavaScript dot navigation. It’s accompanied by a play/pause button that has a time indicator. I am going to show you how to recreate that and even improve it with a help of the GSAP GreenSock library.

Slider from Museum of Science + Industry Chicago.Slider from Museum of Science + Industry Chicago.

Let’s take a closer look. If I click pause at any time and then play again, it jumps to the next step. I’d assume the timer would continue rotating until it reaches a full circle and only then we would get to the next slide. Maybe the creator would tell me it’s a feature and not a bug, but I’d like to disregard this assumption for now.

After a quick source code inspection in the browser, it looks like the carousel is powered by an obsolete and outdated jQuery plugin called Cycle. Don’t worry, we are going to build the whole thing from scratch. The result will look like this:

Our GSAP animated timer — pause works as expected.Our GSAP animated timer — pause works as expected.

HTML

We need the HTML structure first. How would you build it? What elements you would use? I decided to use the following HTML for the timer with dots displayed above:

<div class="carousel-nav">
  <div class="timer">
    <svg width="60" height="60" viewBox="0 0 60 60">
**      <circle cx="30" cy="30" r="29" fill="none" stroke="#ffffff" stroke-width="1" stroke-dasharray="182.212" stroke-dashoffset="0">  
      </circle>**
    </svg>
    <svg width="60" height="60">
      **<circle id="circle" cx="30" cy="30" r="28" fill="none" stroke="#ffffff" stroke-width="4" data-time="5" style="stroke-dashoffset: 0">
      </circle>**
    </svg>
    <span class="play" id="timer-btn"></span>
  </div>

  <ul class="dots">
    <li class=""><span></span></li>
    <li class=""><span></span></li>
    <li class=""><span></span></li>
    <li class="active"><span></span></li>
    <li class=""><span></span></li>
  </ul>
</div>

There are 2 SVG circles for the timer. One is for the thin outside stroke, and another would serve us for the time indication. Notice the stroke-dashoffset and stroke-dasharray attributes. We are going to animate the thicker stroke by changing stroke-dashoffset value, but we will get to it later.

The image slider itself could consist of several div elements where we set the background image for each. For CSS styling, please refer directly to the demo.

<div class="bg-imgs">
  <div style='background-image: url("http://placehold.jp/14/597999/003366/800x600.jpg"); display: none;' class="bgimg">

  <div style='background-image: url("http://placehold.jp/14/597999/003366/800x600.jpg"); display: none;' class="bgimg">

  <div style='background-image: url("http://placehold.jp/14/597999/003366/800x600.jpg"); display: none;' class="bgimg">

  <div style='background-image: url("http://placehold.jp/14/597999/003366/800x600.jpg"); display: none;' class="bgimg">
</div>

The dots navigation

Before we get to the timer let’s prepare basic dot navigation first. (The code snippets contain also jQuery because this is actually something I worked on a few years ago).

We can start by defining some variables:

var allImgs = $(“.bg-imgs > div”);
var allDots = $(“.dots li”);
var currentStep = 0;

Then we add the functions to change the background by clicking on the dots:

function changeBackground() {
  allImgs.fadeOut(800);
  allImgs.eq(currentStep).fadeIn(800);
  allDots.removeClass("active");
  allDots.eq(currentStep).addClass("active");
}

allDots.on("click", function() {
  var btnIndex = $(this).index();
  currentStep = btnIndex;
  changeBackground();
});

$(".dots li").eq(0).addClass("active");

By clicking on the dot, we get its index (0–3 since we have 4 images), setting it as a currentStep and changing the background based on that value (hiding all images and displaying the current one).

Animated timer with GSAP

GSAP is a professional-grade JavaScript animation for the modern web.

It’s a standard for web animations and the library is fast and packed with lot of features. There are even plugins you can use to create other extra cool stuff. We just need the basics though.

Let’s add more variables to control the timer.

var circle = document.getElementById(“circle”);
var button = document.getElementById(“timer-btn”);

var tl = gsap.timeline();  // GSAP v3
var intervalTimer;
var timeLeft;
var wholeTime = 8;
var isPaused = false;
var isStarted = false;
var radius = 28;
var initialOffset = Math.PI * 2 * radius;  // Circumference

Note: The GSAP API has changed so you might encounter instances of v2 where TimelineLite or TimelineMax constructors are used instead.

[Circumference = *π* × diameter = 2*π* × radius.](https://cdn.hashnode.com/res/hashnode/image/upload/v1621430517621/6LhGpfqA7.html)[Circumference = π × diameter = 2π × radius.](en.wikipedia.org/wiki/Circumference#/media/..)

We define the radius and initialOffset variable that represent the circumference of the circle.

As mentioned before, the SVG circle element has stroke-dasharray attribute that we are going to animate. For the radius of 28, we will get the value 175.92 and we want to change it to 0.

If you look at the #circle element, you will see also a data-time attribute that we added to determine how many seconds the animation should last.

We initialize 2 functions animateTimer() and pauseTimer().

if (circle) {
  wholeTime = parseInt(circle.dataset.time, 10);
  animateTimer();
  pauseTimer();
}

button.addEventListener(“click”, pauseTimer);

In the animateTimer() function, we use GSAP timeline *.to())* to animate the strokeDashoffset of the SVG circle. We define the target (our #circle), pass the time (duration), set strokeDashoffset to the circle’s circumference as an initial value, and a few other properties like repeat: -1 to play it infinitely.

function animateTimer() {
  tl.to("#circle", {
    duration: wholeTime
    startAt: {
      strokeDashoffset: initialOffset
    },
    strokeDashoffset: 0,
    repeat: -1,
    ease: "linear",
    onComplete: tl.invalidate
  });
}

In the pauseTimer() function below (I could have named it better), we basically check the state of the timer. Has it started or is it paused? Based on that we toggle the button classes (play/pause) and we control the timeline instance.

tl.paused(boolean) )— Gets or sets the animation’s paused state which indicates whether or not the animation is currently paused.

function pauseTimer() {
  if (isStarted === false) {
    timer(wholeTime);
    isStarted = true;
    button.classList.remove("play");
    button.classList.add("pause");
    tl.paused(false);
  } else if (isPaused) {
    button.classList.remove("play");
    button.classList.add("pause");
    timer(timeLeft);
    isPaused = isPaused ? false : true;
    tl.paused(isPaused);
  } else {
    button.classList.remove("pause");
    button.classList.add("play");
    clearInterval(intervalTimer);
    isPaused = isPaused ? false : true;
    tl.paused(isPaused);
  }
}

In the code above, I also call timer() function, which sets the interval. The function inside *setInterval()* method is executed every 10 milliseconds. We calculate the time that’s left and after 1 full iteration is done we restart the timeline and update the currentStep.

function timer(seconds) {
  var remainTime = Date.now() + seconds * 1000;

  intervalTimer = setInterval(function() {
    timeLeft = ((remainTime — Date.now()) / 1000).toFixed(2);

    if (timeLeft <= 0) {
      tl.restart();
      clearInterval(intervalTimer);
      isStarted = false;
      currentStep = currentStep + 1 < allImgs.length ? currentStep + 1 : 0;
      pauseTimer();
      changeBackground();
      return;
    }
  }, 10);
}

Check out the demo.

Summary

We created from scratch the JavaScript slider with the timer and dot navigation. We pretty much replicated the one from the website of the Museum of Science and Industry of Chicago. Greensock GSAP animation library helped us to seamlessly animate the circle stroke so it looks flawless.

Feel free to play around with the demo and try different animations. You might also refactor and improve the code as I only upgraded GSAP snippets to reflect the newest version of the library. I’d like to see your creations!

More content at plainenglish.io