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.