As site information density grows, designers are challenged to create new ways of visualizing and navigating data. One way of providing an overview of site content is to display a grid of random thumbnail images, with each image corresponding to a site page or article. When used in the past, this kind of mosaic has been static, and often fails to communicate the dynamic, complex nature of a modern site.
For the next redesign of this blog I wanted to create a 404 page with an animated mosaic of article image thumbnails to communicate the themes of the site. I wanted the animation to appear somewhat random, with each cell in the mosaic flicking to a new image independently of the others.
Gaining The Images
The first issue is getting all the images. Obviously, you need a large “stock” of thumbnails to choose your random images from: in this example the displayed set will total 54 pictures. You could gain the thumbnails from any source you wished: a folder of images that have been batch-processed with PhotoShop*; drawing content from a photo service such as flickr or 500px using an API, or using a MySQL query to randomly draw images from a database. I’ve covered most of those techniques in past articles, so how you gain the images won’t be our concern here: in the code below, I’ve assumed a database call.
We need to arrange the images in groups, with a stack of pictures in each group. The images will be placed inside <span>
elements, all contained inside an HTML5 <header>
.
The PHP
Within the loop that prints the results of the query to the web page I’ve used a modulo comparison to place three images in each <span>
before it is closed. I’ve also generated a random number between 1 and 5, and appended this number to a class
attribute for each span: writing CSS that references these classes later will allow us to create the impression of random animation.
<header>
<?php $mosaic = $db->store_result();
$counter = 0;
while($result = $mosaic->fetch_assoc()) {
$random = rand (1 , 5);
if (($counter % 3) == 0) {
echo '<span class="mosaic'.$random.'">';
}
echo '<img src='.$result['thumbnail'].' alt="'.$result['title'].'">';
if (($counter % 3) == 2) {
echo '</span>';
}
$counter++;
}
$mosaic->free(); ?>
</header>
The HTML result
Provided with a pool of images, the PHP would produce an HTML result that looked something like the following:
<header>
<span class="mosaic2">
<img src="los-angeles.jpg" alt="Music to Code By">
<img src="checklist.jpg" alt="A Site Maintenance Checklist">
<img src="bill-paxton-aliens.jpg" alt="Alien Political Class Conflicts">
</span>
<span class="mosaic3">
<img src="ligature-small.png" alt="CSS3 Ligatures and Kerning Pairs">
<img src="css3-rotate.jpg" alt="CSS3 2D Transformations: rotate">
<img src="carolina-de-bartolo.jpg" alt="Explorations In Typography">
</span>
...
</header>
The CSS
We’ll make the <header>
768px wide, which is a useful number for equal division: each <span>
will be 128px square, which allows us to fit exactly six <span>
elements per row. (If you wanted to make the mosaic responsive, each <span>
would be 16.66% of the width of the <header>
container).
header {
width: 768px;
padding: 0;
}
header span {
display: inline-block;
width: 128px;
height: 128px;
position: relative;
margin: 0;
}
header span img {
width: 128px;
height: 128px;
position: absolute;
}
Adding Animation
Now the CSS (displayed here without vendor prefixes to save on space). The entire animation will depend on just one keyframe sequence:
@keyframes mosaic {
0% { opacity: 1; }
25% { opacity: 1; }
33% { opacity: 0; }
100% { opacity: 0; }
}
It’s a very simple set of directives: keep the affected element solid for ¼ of the total length of the animation, fade it out over 8% of the time, and then keep it transparent until the end of the animation sequence.
The only images to be animated will be the last image and the one in the middle of each stack: the last image will appear on “top” due to absolute positioning, and we don’t wish to alter the first image, as there is nothing underneath it.
To start, we’ll animate the two images at the top of each stack the same way by using a specialized attribute and nth-child selector:
span[class^="mosaic"] img:nth-child(3),
span[class^="mosaic"] img:nth-child(2) {
animation-name: mosaic;
animation-duration: 12s;
animation-iteration-count: infinite;
}
As a default setup, this isn’t a great start: both of the top images in every <span>
will fade out at exactly the same time. We’ll add variety to this by addressing the images via the randomly generated number at the end of each class; a few examples should be enough to impart the general idea:
.mosaic1 img:nth-child(3) {
animation-direction: alternate;
}
.mosaic1 img:nth-child(2) {
animation-duration: 8s;
animation-delay: 4s;
}
.mosaic2 img:nth-child(3) {
animation-delay: 2s;
}
.mosaic2 img:nth-child(2) {
animation-delay: 6s;
}
.mosaic3 img {
animation-duration: 21s;
}
.mosaic3 img:nth-child(3) {
animation-delay: 7s;
}
.mosaic3 img:nth-child(2) {
animation-delay: 14s;
}
As you can see, each class
is given a slight variation: starting a little later, reversing direction at the end of the animation sequence, and so on. Combined together, this gives the randomized result you see at the top of this article.
Possible Improvements
There are, as always, some improvements that could be made: in theory, an image sprite could be used as an alternative to loading individual images, reducing the number of HTTP requests to 1. The drawback is CSS that cannot currently animate background images with the same flexibility as it can image elements, and the CSS to position the images would be burdensome. I’ve shown how to make “true” image sprites in the past by using the clip
property, which would allow more options for animation, but still produce an awful lot of CSS.
There are many more possibilities, and I look forward to seeing your variations on the technique.
*Technically there’s no absolute requirement for all the thumbnail images to be the same size. So long as you could guarantee that they were all at least as tall as they were wide, you could simply make the images the same width with CSS and clip them by using overflow: hidden on the span elements.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.