BABS CRAIG

Create a Halo 5 Inspired Loading Icon With HTML/CSS and Vanilla Javascript

blogpost coverJune 13, 2018
JavaScript, Front-End

Recently, I've enjoyed playing around with vanilla Javascript and trying out things that might have been a bit challenging for me a while back. I was playing Halo 5 the other day on Xbox Live and I noticed that the loaders were kind of interesting. I started to think about how I'd implement a loader like that in the browser. A couple days later I whipped out my Codepen and took a stab at it. Here's how it went.

First the easy part. The HTML. In the .html file, we'll create a div with four other divs nested within it - each with an id that corresponds to their box number.

<div class="loader">
  <div class="box" id="box1"></div>
  <div class="box" id="box2"></div>
  <div class="box" id="box4"></div>
  <div class="box" id="box3"></div>
</div>

Next, open up your .css file.

We'll start by creating some variables. This will help us quickly change the size and spacing of the loader and the box elements later by only requiring us to make a change to these two variables.

:root {
  --box-gutter: 2.5px;
  --box-size: 100px;
}

We declare a variable for the box gutter, which is the margin between each box, and one for the box size which is the length of each square.

Next we style the .loader div. We give it a height that is twice the box-size and twice the gutter-size and a width that's the same (since it is a square). I've used css calc() to compute the values from the variables we declared earlier. We then rotate it 45 degrees to the right so the element sits on it's point and gives us the diamond shape effect we're looking for. Lastly we do some float clearing stuff since we're using floats in this example.

.loader {
  width: calc(var(--box-size) * 2 + var(--box-gutter) * 2);
  height: calc(var(--box-size) * 2 + var(--box-gutter) * 2);
  transform: rotate(45deg);
  overflow: auto;
}

.loader::after {
  content: "";
  clear: both;
  display: table;
}

Styling for the boxes is easy, we set the width and height on the .box class so it applies to all four boxes and then we take each box and give it specific styling since they all have different margins based on their positions. This gives us the correct gutter or spacing between the boxes.

.box {
  float: left;
  width: var(--box-size);
  height: var(--box-size);
}

#box1 {
  margin: 0 var(--box-gutter) var(--box-gutter) 0;
}

#box2 {
  margin: 0 0 var(--box-gutter) var(--box-gutter);
}

#box3 {
  margin: var(--box-gutter) 0 0 var(--box-gutter);
}

#box4 {
  margin: var(--box-gutter) var(--box-gutter) 0 0;
}

At this point your css file should look like so:

/* set up some vaiables to help us quickly change the size of the component  */

:root {
  /* the gutter spacing between the inner squares */
  --box-gutter: 2.5px;
  /* the length of each side of the inner squares */
  --box-size: 100px;
}

.loader {
  /* loader width is (--box-size * 2) + (--box-gutter * 2). This maintains the aspect ratio */
  width: calc(var(--box-size) * 2 + var(--box-gutter) * 2);
  height: calc(var(--box-size) * 2 + var(--box-gutter) * 2);
  margin: 100px auto;
  transform: rotate(45deg);
}

.box {
  width: var(--box-size);
  height: var(--box-size);
}

#box1 {
  float: left;
  margin: 0 var(--box-gutter) var(--box-gutter) 0;
}

#box2 {
  float: left;
  margin: 0 0 var(--box-gutter) var(--box-gutter);
}

#box3 {
  float: left;
  margin: var(--box-gutter) 0 0 var(--box-gutter);
}

#box4 {
  float: left;
  margin: var(--box-gutter) var(--box-gutter) 0 0;
}

Now it's time for the Javascript. In your .js file…

const boxes = {
  box1: document.querySelector("#box1"),
  box2: document.querySelector("#box2"),
  box3: document.querySelector("#box3"),
  box4: document.querySelector("#box4")
};

We save a reference to each of our box divs in a boxes object. This allows us easily map through the boxes as we will see later.

let currentBoxNumber = 0;
const colorChangeIntervalInMs = 500;
// We create a mapping from the keys in the box object to box
// numbers. ["box1", "box2", ... ] becomes [1, 2, ... ]
const listOfBoxNumbers = Object.keys(boxes).map((_, index) => index + 1);
const defaultColor = "#9eedff";
const highlightColor = "#1fcdf5";

Next we declare a currentBoxNumber variable to hold the number of the current box and a colorChangeIntervalInMs variable which is the amount of time in milliseconds to wait before the boxes change color. Lastly, we create a list of box numbers. First we use Object.keys to get an array of box keys then we map through them and get an array with numbers for each of the boxes which will look like so [1,2,3,4]. We get each number by simply adding 1 to the index of each box. We also specify our defaultColor and highlightColor for the boxes.

// Save the interval into a constant so we can clear it later as needed
const loadingInterval = setInterval(() => {
  currentBoxNumber += 1;
  if (currentBoxNumber === 5) {
    listOfBoxNumbers.forEach(boxNumber => {
      boxes[`box${boxNumber}`].style.backgroundColor = highlightColor;
    });
    currentBoxNumber = 0;
    return;
  }

  listOfBoxNumbers.forEach(boxNumber => {
    boxes[`box${boxNumber}`].style.backgroundColor = defaultColor;
  });

  boxes[`box${currentBoxNumber}`].style.backgroundColor = highlightColor;
}, colorChangeInterval);

Finally we create an interval using the setInterval() function. The interval starts by adding 1 to the currentBoxNumber. We then check to see if the currentBoxNumber is greater than 5. In this case, we set the background color for all the boxes to the highlightColor, reset the currentBoxNumber to 0 and return. This is what we want to happen after all the boxes have each changed color.

Look at this line carefully:

listOfBoxNumbers.forEach(boxNumber => {
  boxes[`box${boxNumber}`].style.backgroundColor = highlightColor;
});

Here, we take our listOfBoxNumbers and loop through them. Then for each boxNumber we look for the box in the boxes object using a combination of bracket-notation access and string interpolation. For instance, if boxNumber is equal to 1 then box${boxNumber} will give us "box1" (and so on).

If the currentBoxNumber is less than 5 we map through each box and apply the defaultColor then we take the box with the currentBoxNumber and apply the highlightColor to it.

The interval will run for as long as we don't clear it and will continually run in a loop. The currentBoxNumber will go from 1 to 5 before getting reset to 0 once again.

To clear the interval, we can call clearInterval() and pass in a reference to the interval we created like so:

clearInterval(loadingInterval);

And that's it! You should now have a loader that looks and behaves like the one above. You can also play around with stuff like the colors, size and speed. Having the important values stored in variables will make it easy to tweak and play around with things.

Babs is a JavaScript developer who writes mostly React/React Native & NodeJS (with a healthy dose of GraphQL) by day and everything else JavaScript under the cover of night. You can find him on Twitter and Instagram where he shares details of his clandestine love affair with Software Development.

Subscribe to my mailing list

© 2021, Built with Gatsby 💜