DEV Community

Cover image for Fun JS - Start multiple counters ⏱️ at different speeds
Gass
Gass

Posted on • Updated on

Fun JS - Start multiple counters ⏱️ at different speeds

Sometimes I think I'm a Stack Overflow junky 🤪... And my addiction was worse a few months ago when I saw this question and couldn't resist my fingers from tackling it.

The main idea was to..

🛠️ Build a JavaScript algorithm that would make counters start at different speeds depending on the value set in the speed attribute of the HTML span tag elements.

I feel that is an interesting and fun code to share with others.

If you want to try out the snippet 👉 click here


Before you continue..

I've written this post in great detail so that it can be understood by begginers. The idea of the post is to have fun programming, learn/practice a few things, experiment and maybe come up with a different or even a better solution that the one I came up with.


If(you_want_to_continue === true) console.log('keep scrolling..')
else console.log('see you! 👋') 
Enter fullscreen mode Exit fullscreen mode

Grab some ☕ and lets get started!

Go to your favorite JS code editor (JSFiddle is pretty cool) and setup the HTML

<h1 class="counter" speed='3'></h1>
<h1 class="counter" speed='7'></h1>
<h1 class="counter" speed='10'></h1>
<h1 class="counter" speed='12'></h1>
Enter fullscreen mode Exit fullscreen mode

As you can see, I added the speed attribute to the h1 elements. The class counter is to let JS know which elements we want to select using the querySelectorAll()


Now that the HTML code is ready is time to start programming

Let's declare the elements const

const elements = document.querySelectorAll('.counter')
Enter fullscreen mode Exit fullscreen mode

💬 For those who don't know, the document method querySelectorAll() returns a static NodeList representing a list of the document's elements that match the specified group of selectors.

🧐 So, in this case elements will contain all h1 elements that have the class counter.

console.log(elements) outputs:

{
  "0": <h3 class="counter" speed="6"></h3>,
  "1": <h3 class="counter" speed="7"></h3>,
  "2": <h3 class="counter" speed="8"></h3>,
  "3": <h3 class="counter" speed="9"></h3>,
  "item": function item() {
    [native code]
},
  "keys": function keys() {
    [native code]
},
  "values": function values() {
    [native code]
},
  "entries": function entries() {
    [native code]
},
  "forEach": function forEach() {
    [native code]
},
  "length": 4
}
Enter fullscreen mode Exit fullscreen mode

This doesn't look very nice, I know.. But accessing the elements is simple.

They can be accessed by their 🗝️ key: elements[key]. For example elements['1'] returns the following HTML element:

<h3 class="counter" speed="7"></h3>
Enter fullscreen mode Exit fullscreen mode

Now that we understand how to retrieve and work with the static NodeList elements let's start to build the logic by creating a function that will set intervals for every HTML element in elements.

function setCounter(element, speed, limit){
  let count = 0  // every count will start from 0

  let interval = setInterval(() => {
    // in every interval the count will increment by the speed value 
    count += speed

    // update the HTML in this specific element  
    element.innerHTML = count

    // stop the setInterval() if condition is met
    if(count >= limit) clearInterval(interval)

  }, 1) // <--- the interval will loop every 1 millisecond
}
Enter fullscreen mode Exit fullscreen mode

Now that the setCounter() function is ready we need to loop through all the elements to retrieve their speed, and invoke the setCounters() but before doing that, we need to be clear on how to get and use all speeds.


To retrieve the speed we need to specify from which element are we going to get the attribute from:

elements[key].getAttribute('speed')

💬 When using .getAttribute() the returned value comes as a string. This can be prooven by using typeof like so:

let key = '1'
let value = elements[key].getAttribute('speed')
console.log(typeof value) // --> string
Enter fullscreen mode Exit fullscreen mode

To make the code work, we need to convert to integer by using Number() or parseInt().

After this we have all needed to invoke setCounter(element, speed, limit). This will initialize each setInterval with their respective parameters.

elements.forEach((el, key) => {
  let speed = Number(elements[key].getAttribute('speed'))
  setCounter(el, speed, 1000) 
})
Enter fullscreen mode Exit fullscreen mode

Now that the code is completed do you think is going to work properly ?

Let's give it a shot and see what happens. Take into consideration that the limit passed as a parameter to setCounter() is 1000

Code execution

It didn't work properly 😭... The setIntervals are working but they are showing a bigger count than the limit. This is because the value of the speed that is added to the count is not summing 1 in every loop, it can be any number so it can overflow the limit.

One way of solving this, is by using a ternary operator inside the setInterval(), like so:

count = (count >= limit) ? limit : count + speed
Enter fullscreen mode Exit fullscreen mode

How does this work?

If count is bigger or equal to limit, count will equal to limit and in the opposite case count will equal count + speed.

This is a minor patch to solve the last loop count problem. By doing this the count will never overflow the limit.

The resulting code ✔️

const elements = document.querySelectorAll('.counter')

elements.forEach((el, key) => {
  let speed = Number(elements[key].getAttribute('speed'))
  setCounter(el, speed, 1000)
})

function setCounter(element, speed, limit){
  let count = 0
  let interval = setInterval(() => {
    count = (count >= limit) ? limit : count + speed
    if(count === limit) clearInterval(interval)
    element.innerHTML = count
  }, 1)
}
Enter fullscreen mode Exit fullscreen mode

This is my first post in dev.to

Feel free to experiment/play with the code and share your unique approach 👍 if you have one 🙃

Top comments (0)