Using setInterval for creating animation

Viewed 80

I am trying to build this graph utility and using setInterval for animation on bars height.
I do not how to achieve this using CSS Transition but I am also not sure how efficient this is using javascript.

While the code works properly (atleast on my end) it seems like it is pretty cpu intensive.

    let scaleFrag = new DocumentFragment();
    for(let i = 100; i > -1; i-=5){
        let div = document.createElement('div');
        div.innerHTML = i;
        scaleFrag.append(div);
    }

    document.getElementsByClassName('scale')[0].append(scaleFrag);
    let graph = getComputedStyle(document.getElementsByClassName('graph')[0]);
    let arblenth = Math.floor(Math.random()*50);
    let array = [];
    for(let i=0; i < arblenth; i++){
        array.push(Math.floor(Math.random()*101));
    }
    let gw = parseInt(graph.width, 10);
    let gh = parseInt(graph.height, 10);
    gw = gw - 2*arblenth;
    console.log(graph.width)
    let barFrag = new DocumentFragment();
    for(let i=0; i < array.length ; i++){
        let cl = document.createElement('div');
        cl.innerHTML = array[i];
        cl.className = i%2 == 0 ? 'cl-o' : 'cl-e';
        cl.style.width = gw + 'px';
        barFrag.append(cl);
    }
    document.getElementsByClassName('graph')[0].append(barFrag);

    function animateHeight(cls) {
        let cl = document.getElementsByClassName(cls);
        
        for(let i = 0; i < cl.length; i++){
            let maxHeight = Math.floor(gh*(parseInt(cl[i].innerHTML)/100));
            let h = 0;
            let si = setInterval(() => {
                if(h == maxHeight){
                    clearInterval(si);
                }
                
                cl[i].style.height = h++ + 'px';
            },0.1)
        }
    }
    animateHeight('cl-o');
    animateHeight('cl-e');
html, body  {
    height: 100%;
    width: 100%;
    margin: 0px;
    padding: 0px;
    font-family: sans-serif;
}
* {
    box-sizing: border-box ;
}
body {
    padding-top: 5vh;
}
.grid {
    height: 90vh;
    width: 70vw;
    margin: 0 auto;
    text-align: center;
    box-shadow: 3px 4px 15px -2px rgba(49,41,41,0.9);
    padding: 1rem;
    display: flex;
    justify-content: center;
    align-items: center;
}
.scale {
    width: 5%;
    height: 90%;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    border-right: 1px solid grey;
}

.graph {
    width: 90%;
    height: 90%;
    border-left: none;
    display: flex;
    align-items: flex-end;
}
.cl-o{
    background-color: limegreen;
    transition: height;
}
.cl-e{
    background-color: lightslategray;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Portfolio</title>

    <link rel='stylesheet' href="./style.css">
</head>
<body>
    <div class="grid">
        <div class="scale"></div>
        <div class="graph">
        </div>
    </div>
</body>
</html>

I have tried this 500 bars of whom heights are set randomly and it still seems to work. Should I proceed this way or it could be improved ?

2 Answers

It would be better to use CSS transitions in this case.

Here is what the css would look like:

.cl-o{
   background-color: limegreen;
   height: 0px;
   transition: 1s all;
}
.cl-e{
    background-color: lightslategray;
    height: 0px;
    transition: 1s all;
}

So here the height is initially 0, if you instantly set the height in JavaScript, there is no animation (I only tried this on CodePen), so you can use a very small timeout. This is the new animateHeight function.

function animateHeight(cls) {
    let cl = document.getElementsByClassName(cls);
    
    for(let i = 0; i < cl.length; i++){
        let maxHeight = Math.floor(gh*(parseInt(cl[i].innerHTML)/100));
        setTimeout(() => {
          cl[i].style.height = maxHeight + "px"
        }, 10)
    }
}

Some minor changes I did to the code:

  1. Refactored code into methods, so it's easier to read.

  2. Replaced document.getElementsByClassName with document.getElementById and document.querySelectorAll('#graph > div').

function createScaleFrag() {
  let scaleFrag = new DocumentFragment();

  for (let i = 100; i > -1; i -= 5) {
    let div = document.createElement('div');
    div.innerHTML = i;
    scaleFrag.append(div);
  }
  
  return scaleFrag;
}

function createBarFrag() {
  let arblenth = Math.floor(Math.random() * 50);
  let array = [];

  for (let i = 0; i < arblenth; i++) {
    array.push(Math.floor(Math.random() * 101));
  }

  gw = gw - 2 * arblenth;

  let barFrag = new DocumentFragment();
  for (let i = 0; i < array.length; i++) {
    let cl = document.createElement('div');
    cl.innerHTML = array[i];
    cl.className = i % 2 == 0 ? 'cl-o' : 'cl-e';
    cl.style.width = gw + 'px';
    barFrag.append(cl);
  }
  
  return barFrag;
}

document.getElementById('scale').append(createScaleFrag());

const grafElement = document.getElementById('graph');
let graph = getComputedStyle(grafElement);
let gw = parseInt(graph.width, 10);
let gh = parseInt(graph.height, 10);

grafElement.append(createBarFrag());

function animateHeight(cl) {
  for (let i = 0; i < cl.length; i++) {
    let maxHeight = Math.floor(gh * (parseInt(cl[i].innerHTML) / 100));
    let h = 0;
    let si = setInterval(() => {
      if (h == maxHeight) {
        clearInterval(si);
      }

      cl[i].style.height = h++ + 'px';
    }, 0.1)
  }
}

animateHeight(document.querySelectorAll('#graph > div'));
html,
body {
  margin: 0px;
  padding: 0px;
  font-family: sans-serif;
}

* {
  box-sizing: border-box;
}

.grid {
  height: 90vh;
  width: 70vw;
  margin: 0 auto;
  text-align: center;
  box-shadow: 3px 4px 15px -2px rgba(49, 41, 41, 0.9);
  padding: 1rem;
  display: flex;
  justify-content: center;
  align-items: center;
}

#scale {
  width: 5%;
  height: 90%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  border-right: 1px solid grey;
}

#graph {
  width: 90%;
  height: 90%;
  border-left: none;
  display: flex;
  align-items: flex-end;
}

.cl-o {
  background-color: limegreen;
  transition: height;
}

.cl-e {
  background-color: lightslategray;
}
<div class="grid">
  <div id="scale"></div>
  <div id="graph">
  </div>
</div>

The trouble with your code is that you execute as many intervals as columns in the graph, which (I presume) force a repaint for every single update. What you should aim to do is instead do all the calculations, and then update the screen once. You do that by using requestAnimationFrame.

The code is self-explanatory.

function createScaleFrag() {
  let scaleFrag = new DocumentFragment();

  for (let i = 100; i > -1; i -= 5) {
    let div = document.createElement('div');
    div.innerHTML = i;
    scaleFrag.append(div);
  }
  
  return scaleFrag;
}

function createBarFrag() {
  let arblenth = Math.floor(Math.random() * 50);
  let array = [];

  for (let i = 0; i < arblenth; i++) {
    array.push(Math.floor(Math.random() * MAX_COLUMN_HEIGHT));
  }

  gw = gw - 2 * arblenth;

  let barFrag = new DocumentFragment();
  for (let i = 0; i < array.length; i++) {
    let cl = document.createElement('div');
    cl.innerHTML = array[i];
    cl.className = i % 2 == 0 ? 'cl-o' : 'cl-e';
    cl.style.width = gw + 'px';
    barFrag.append(cl);
  }
  
  return barFrag;
}

const MAX_COLUMN_HEIGHT = 101;

document.getElementById('scale').append(createScaleFrag());
const grafElement = document.getElementById('graph');
let graph = getComputedStyle(grafElement);
let gw = parseInt(graph.width, 10);
let gh = parseInt(graph.height, 10);

grafElement.append(createBarFrag());

function animateHeight(cl) {
  const ANIMATION_DURATION = 2000;
  let start;
  
  function updateHeight(timestamp) {
    if (start === undefined) { start = timestamp; }
    
    let elapsed = timestamp - start;
    let progress = elapsed / ANIMATION_DURATION;
    let globalHeight = progress * MAX_COLUMN_HEIGHT;
    
    let maxHeight, shouldContinueGrow;
    
    for (let i = 0; i < cl.length; i++) {
      maxHeight = parseInt(cl[i].innerHTML);
      shouldContinueGrow = globalHeight <= maxHeight;
      
      if (shouldContinueGrow) {
        cl[i].style.height = gh * progress + 'px';
      }
    }
    
    if (elapsed < ANIMATION_DURATION) {
      requestAnimationFrame(updateHeight);
    }
  }
  
  requestAnimationFrame(updateHeight);
}

animateHeight(document.querySelectorAll('#graph > div'));
html,
body {
  margin: 0px;
  padding: 0px;
  font-family: sans-serif;
}

* {
  box-sizing: border-box;
}

.grid {
  height: 90vh;
  width: 70vw;
  margin: 0 auto;
  text-align: center;
  box-shadow: 3px 4px 15px -2px rgba(49, 41, 41, 0.9);
  padding: 1rem;
  display: flex;
  justify-content: center;
  align-items: center;
}

#scale {
  width: 5%;
  height: 90%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  border-right: 1px solid grey;
}

#graph {
  width: 90%;
  height: 90%;
  border-left: none;
  display: flex;
  align-items: flex-end;
}

.cl-o {
  background-color: limegreen;
  transition: height;
}

.cl-e {
  background-color: lightslategray;
}
<div class="grid">
  <div id="scale"></div>
  <div id="graph"></div>
</div>

Related