Search code examples
javascriptcssanimationsetintervalcountdown

Javascript animated countdown


I've been trying to create a countdown that animates the text every time the counter changes. I can execute the change at the start but not sure how to clear the change and repeat it.

HTML & CSS

<!DOCTYPE html>
<html>
<head>
    <!--meta http-equiv="refresh" content="4; url=index.php"-->
    <style type="text/css">
        p
        {
            transition: 1s;
            font-size: 100%;
        }
    </style>
</head>
<body>

<p>A script on this page starts this clock:</p>
<p id="demo">Login in....</p>

<script>

    //various javascript here

</script>
</body>
</html>

This script changes the CSS once on the first count but not on count 2 or 1.

//Set the counter variable
    var counter= 4;

    //Set the timer to count the counter down
    var timer = setInterval(function() 
    {
        counter--;
        if(counter== 0) 
        {
            clearInterval(timer);
        } 
        else 
        {
            //Execute the CSS change function
            styleChange();
        }
    }, 1000);

    function styleChange()
    {
        document.getElementById("demo").innerHTML = (counter);
        document.getElementById("demo").style.fontSize = "500%";
        document.getElementById("demo").style.opacity = "0";
        // In CSS "Demo" is given a transition of 1s to smooth out the animation"
    }

The next script doesn't reset the style each time the counter but toggles between styles, not what I'm trying to do but at least it repeats unlike the first script. It just affects the style, if it worked I would add the timer.

    var myVar = setInterval(setStyle, 1000);

    function setStyle() {
      var x = document.getElementById("demo");
      x.style.fontSize = x.style.fontSize == "100%" ? "500%" : "100%";
    }

    function stopStyle() {
      clearInterval(myVar);
    }

I thought of trying to use a "for" to loop through the values of the counter, change the style on a value change then reset the value after but I couldn't get it to work. Visually the effect should look like this but played in reverse and not on acid-> https://www.youtube.com/watch?v=hsUKu9_Lr6U. I've borrowed heavily from w3schools pages about setInterval and clearInterval.


Solution

  • You don't need two separate intervals to make the text bigger and smaller.

    Instead, you could use CSS transitions to animate the font-size from small to big and 2 nested Window.requestAnimationFrame() calls to time things properly, as shown below:

    const counter = document.getElementById('counter');
    let value = 11;
    
    const intervalID = setInterval(() => {
      const nextValue = --value;
      
      if (nextValue === -1) {
        clearInterval(intervalID);
        
        return;
      }
      
      requestAnimationFrame(() => {
        // Update the value and remove the `big` class in the next frame, so that
        // the text becomes smaller again:
        counter.textContent = nextValue;
        counter.classList.remove('big');
      
        requestAnimationFrame(() => {
          // One more frame after that (so that the element has time to be re-rendered
          // with the smaller font-size, add the `big` class again:
          counter.classList.add('big');
        });
      });
      
    }, 1000);
    body {
      margin: 0;
      display: flex;
      height: 100vh;
      justify-content: center;
      align-items: center;
      font-family: monospace;
    }
    
    #counter.big {
      font-size: 500%;
      opacity: 0;
      transition: all linear 1s;
    }
    <div id="counter" class="big">10</div>

    You could even use CSS custom properties to easily change the interval delay while keeping it in-sync with the CSS transition-duration:

    const duration = 0.5; // In seconds
    const counter = document.getElementById('counter');
    let value = 11;
    
    counter.style.setProperty('--transition-duration', `${ duration }s`);
    
    const intervalID = setInterval(() => {
      const nextValue = --value;
      
      if (nextValue === -1) {
        clearInterval(intervalID);
        
        return;
      }
      
      requestAnimationFrame(() => {
        // Update the value and remove the `big` class in the next frame, so that
        // the text becomes smaller again:
        counter.textContent = nextValue;
        counter.classList.remove('big');
      
        requestAnimationFrame(() => {
          // One more frame after that (so that the element has time to be re-rendered
          // with the smaller font-size, add the `big` class again:
          counter.classList.add('big');
        });
      });
      
    }, duration * 1000);
    body {
      margin: 0;
      display: flex;
      height: 100vh;
      justify-content: center;
      align-items: center;
      font-family: monospace;
    }
    
    #counter.big {
      /* Default value: */
      --transition-duration: 1s;
      
      font-size: 500%;
      opacity: 0;
      transition: all linear var(--transition-duration);
    }
    <div id="counter" class="big"></div>