JupiterEarthMarsSaturnNeptuneVenus

and are two separate technologies with individual strengths and weaknesses, and will always remain so. Traditionally, the two have been characterized as controlling “behavior” and “appearance” respectively, but over the last several years CSS has grown significantly in scale, scope and power, causing it to tread on JavaScript’s toes in some areas. As one example, CSS offers smoother, more performant animation,  at the cost of compatibility with older versions of Internet Explorer. Due to these complications, it’s not always clear which option is best for a developer to take: do we create animations purely in CSS, or in JavaScript?

The ideal solution is to use both: employ JavaScript where it works best, and use CSS for the rest. A good example would be my article a year ago on Animating Elements Sequentially, which used JQuery to change a series of photographs. A best practice would be to build the fade in effect for each image in CSS, and apply that using JavaScript.

The HTML

Let’s start with fresh markup:

<div id="planets">
	<img src="jupiter.jpg" alt="Jupiter">
	<img src="earth.jpg" alt="Earth">
	<img src="mars.jpg" alt="Mars">
	<img src="saturn.jpg" alt="Saturn">
	<img src="neptune.jpg" alt="Neptune">
	<img src="venus.jpg" alt="Venus">
</div>

The CSS

With a little code to ensure that the images display well on smaller screens:

div#planets {
	font-size: 0;
	background: #000;
	overflow: hidden;
}
div#planets img {
	float: left;
	width: 33.33%;
}
@media (max-width: 500px) { 
	div#planets img {
		width: 50%;
	} 
}

Each of these images will fade in sequentially over several seconds. Writing a transition for each image would be tiresome in the extreme, but we can describe the transition generally:

div#planets img {
	float: left;
	width: 33.33%;
	opacity: 0;
	transition: .4s opacity;
}

(Note that I’ve kept the code free of vendor prefixes for clarity).

Adding JavaScript

Unlike the previous example, I’m going to use pure JavaScript rather than JQuery to loop through the images in the container: it’s silly to load 100K of a library just to execute an iteration. First, we need to identify just how many images there are. At the end of your page, add a script:


var planets = document.getElementById("planets"),
numplanets = document.querySelectorAll("#planets > img").length;

(The use of querySelector will limit this solution to IE 8 and above, although there’s longer and more traditional code you could use if you wanted to gain greater backward compatibility).

Then we need to loop through each image in the container:

var planets = document.getElementById("planets"),
numPlanets = document.querySelectorAll("#planets > img").length,
planetPhotos = planets.childNodes;
for (var i = 0; i < numPlanets; i++) {
	...      
}

Before animating the images, let’s see what we have in our collection of objects. Inside the loop, add:

alert(planetPhotos[i]);

(Or, if you want the result to be less annoying and know a little bit about the console:

console.write(planetPhotos[i]); )

Formatting A Nodelist

Taking a look at the output in a browser may reveal something odd: the nodelist (a collection of all the child elements inside planet) may have some errant additions. In some browsers you’ll see reports of six HTMLImageElement objects together with a number of text objects. We want the former, but not the latter. What’s going on?

The browser is interpreting whitespace as text nodes, and adding them to the collection. That’s annoying, and can be treated in several ways: right now we’ll take the easiest solution, and set font-size inside the <div> to 0:

div#planets { font-size: 0; }

With that done, let’s replace the alert/console write with something useful:

for (var i = 0, numPlanets; i < numPlanets; i++) {
planetPhotos[i].style.opacity = 1;
}

That works, but the result is almost instantaneous for all the images: we want a pause between each transition.

Pausing JavaScript

JavaScript is made to respond to events, not to be shackled: generally speaking we want our scripts to run as fast as possible, without blocking any other activity on the page. For that reason, JavaScript “pauses” are abstracted into two functions: setInterval and setTimeout.

You would think that something like this might work:

setTimeout(planetPhotos[i].style.opacity = 1), 500)

But sadly, no: the timeouts will essentially all execute at the same time.  Instead, we have to abstract things out a little further. The complete script becomes:

function createTimer(i) {
	setTimeout(function() {
planetPhotos[i].style.opacity = 1;
    }, 1000 + 1000 * i)
}
function iterate(numPlanets) {
    for (var i = 0; i < numPlanets; i++) {
    	createTimer(i);
    }
}
var planets = document.getElementById("planets"),
numPlanets = document.querySelectorAll("#planets > img").length,
planetPhotos = planets.childNodes;
iterate(numPlanets);

(Note that the 1000 + 1000 part of the setTimeOut function is present so that the first image is also delayed in appearing on the page).

If you wanted to create greater separation between appearance and behavior, rewrite the new appearance of the images as a class, and use JavaScript to add the class to each image, rather than changing its opacity directly:

div#planets img.wax {
	opacity: 1;
}

And the JavaScript:

function createTimer(i) {
	setTimeout(function() {
planetPhotos[i].className = "wax";
		}, 1000 + 1000 * i)
}

This also allows you to alter the animation very quickly: if you wanted the effect of the images both growing in size and fading in at the same time, as in the example above, simply change the CSS:

div#planets img {
	float: left;
	width: 33.33%;
	opacity: 0;
	transform: scale(0); 
	transition: .4s all;
}
div#planets img.wax {
	opacity: 1;
	transform: scale(1);
}

This result is the best of all possible worlds: gaining smooth animations from CSS while leveraging the power of JavaScript. One drawback is that the code lacks a fallback for IE 8 and 9, which do not support CSS transitions. I’ll demonstrate at a solution for those browsers in the next article.

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/kmHDJ