Fun JS - Start multiple counters ⏱️ at different speeds
Tags: javascript
Published: 2022-04-20 16:19:43

Sometimes I think I'm a Stack Overflow junky 🤪... And my addiction was worse a few months ago when I saw [this question](https://stackoverflow.com/questions/69265494/simple-counters-dataset-not-working-properly) 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](https://stackoverflow.com/questions/69265494/simple-counters-dataset-not-working-properly/69266844#69266844)


---

> ### 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! 👋') 
```
## Grab some ☕ and lets get started! 

Go to your favorite JS code editor ([JSFiddle](https://jsfiddle.net/) is pretty cool) and setup the HTML 

```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>
```

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()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)

---


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

Let's declare the `elements` const

```javascript
const elements = document.querySelectorAll('.counter')
```
💬 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:

```javascript
{
  "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
}
```
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:

```html
<h3 class="counter" speed="7"></h3>
```

---

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`.

```javascript
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
}
```

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:

```javascript
let key = '1'
let value = elements[key].getAttribute('speed')
console.log(typeof value) // --> string
```
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.

```javascript
elements.forEach((el, key) => {
  let speed = Number(elements[key].getAttribute('speed'))
  setCounter(el, speed, 1000) 
})
```
**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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aqljydu20rqi3vb8rilv.gif)

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:

```javascript
count = (count >= limit) ? limit : count + speed
```

### 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** ✔️

```javascript
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)
}
```
---
This is my first post in [dev.to](https://dev.to)

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