I'm Dudley Storey, the author of Pro CSS3 Animation. This is my blog, where I talk about web design and development with HTML, CSS and SVG. To receive more information, including news, updates, and tips, you should follow me on Twitter or add me on Google+.

my books

Pro CSS3 Animation book coverPro CSS3 Animation, Apress, 2013

my other blogs

Massive Head CanonMassive Head Canon: Intelligent discussion of movies, books, games, and technology.

my projects

The New DefaultsThe New Defaults — A Sass color keyword system for designers.

CSSslidyCSSslidy — an auto-generated #RWD image slider. 3.8K of JS, no JQuery.

ImgDex: An HTML5 Rolodex-Style 3D Image Gallery

Before the iPhone there was the Rolodex, a wheeled index card contact management system popular from the late 1950’s to the mid-80s. Recently, it...

Before the iPhone there was the Rolodex, a wheeled index card contact management system popular from the late 1950’s to the mid-80s. Recently, it occurred to me that the device’s UI might be brought into the 21st century and applied to a CSS 3D image gallery.

Instead of a wheel, I decided to use HTML5 range input to flick the images forward and back. JavaScript is used to link the input actions to the orientation of the images. I’ve made the CSS and scripting as adaptive as possible: in theory, an ImgDex could consist of four images or twenty, with no code change required.

The content of the page is a series of images that are all the same size inside <figure> elements with separate <figcaption> descriptions:

<div id="imgdex">
<figure>
<img src="arabic-eyes.jpg" alt="Bedouin headress">
<figcaption>Bedouin</figcaption>
</figure>
<figure>
<img src="blue-green-eyes.jpg" alt="Blue-green-eyes">
<figcaption>Blue-green</figcaption>
</figure>
<figure>
<img src="fake-eyelashes.jpg" alt="Fake eyelashes">
<figcaption>Dramatic Fake</figcaption>
</figure>
<figure>
<img src="snow-queen.jpg" alt="A girl in heavy snow">
<figcaption>Snow</figcaption>
</figure>
</div>

The CSS (sans vendor prefixes) is relatively straightforward:

#imgdex { position: relative; perspective: 4000px; transform-style: preserve-3d; padding-top: 58%; }
#imgdex figure, #imgdex figure figcaption { 
  position: absolute;
  transition: 1s ease-in-out;
}
#imgdex figure { top: 0; left: 120px; transform-origin: left bottom; width: 70%; }
#imgdex figure figcaption { bottom: 0; font-size: 1.2rem; left: -8rem; opacity: 0; }
#imgdex figure:last-of-type{ transform: rotateX(5deg); box-shadow: 0px 0px 200px rgba(0,0,0,0.5); }

The <div> sets up the perspective plane for the manipulation of the images. Every <figure> element except the last will be rotated forward from their lower edges to be almost “flat” with JavaScript. The very last element – the one at the “back” of the stack – is brought upright, leaned back a little, and provided with a box-shadow. This last <figure> won’t move, but the others will.

Below the <div>, a range input:

<input type="range" min="1" onfocus="this.oldvalue = this.value;" oninput="updateImage(this);this.oldvalue = this.value;" id="ranger">

When the slider is moved, the element calls on the updateImage() function and passes its current value and previous value.

But before the slider can be used we need to provide it with an upper limit, corresponding to the total number of <figure> elements, and set the default value to that same number. We’ll place the script to do so at the bottom of the page:

var imgdex = document.getElementById('imgdex'),
figs = imgdex.querySelectorAll('figure'),
imgcount = figs.length;
ranger.max = imgcount;
ranger.value = imgcount;

Finally, we need to move the figures based on the position of the slider. Continuing the script:

for (var i=0; i<(imgcount-1); i++) {
var rotation = parseFloat(-92 + "." + (imgcount - i));
figs[i].style.webkitTransform = 'rotateX(' + rotation + 'deg)';
figs[i].style.transform = 'rotateX(' + rotation + 'deg)';
}
document.querySelector('#imgdex figure:last-child figcaption').style.opacity = 1;
function updateImage(slider) {
var currentimg = document.querySelector('#imgdex figure:nth-child('+slider.value+')');
if (slider.oldvalue !== undefined) {
var oldimg = document.querySelector('#imgdex figure:nth-child('+(slider.oldvalue)+')');
} else {
slider.oldvalue = imgcount;
var oldimg = document.querySelector('#imgdex figure:nth-child('+(slider.oldvalue)+')');
}
if (slider.value < slider.oldvalue) {
currentimg.style.webkitTransform = 'rotateX('+slider.value+'deg)';
currentimg.style.transform = 'rotateX('+slider.value+'deg)';
}
if (slider.value > slider.oldvalue) {
var rotation = parseFloat(-92 + "." + (imgcount - slider.value));
oldimg.style.webkitTransform = 'rotateX(' + rotation + 'deg)';
oldimg.style.transform = 'rotateX(' + rotation + 'deg)';
}
if (slider.value !== slider.oldvalue) {
currentimg.querySelector('figcaption').style.opacity = 1;
oldimg.querySelector('figcaption').style.opacity = 0;
}
}

This code is a little more complex:

  1. First, it rotates all but the very first <:figure> element forward. The amount of rotation decreases slightly for each, so that the images are not perfectly congruent: otherwise, weird flickering and image mashing occurs during animation. As Safari is the only modern browser to still require vendor prefixes for transformation, I've left that option as an additional line.

There are many possible improvements to the code, but this should be enough to get anyone interested started on making their own version. Explore the ImgDex code on CodePen

This site helps millions of visitors while remaining ad-free. For less than the price of a cup of coffee, you can help pay for bandwidth and server costs while encouraging further articles.