The Goal: Create an effect where the content zooms out past the viewer, revealing new content ‘underneath’.
The Rules: No javascript; should work in modern browsers.
The Result: CSS Zoom. Works in the latest versions of Firefox, Chrome, Safari, Opera and IE9. (IE9 doesn’t support the transitions, but that’s OK.)
Caressing HTML
The HTML is pretty straight forward: a container and 4 boxes. (I opted to use HTML5 sections. The full file also has a <nav> element in there to provide internal linking.):
<div id="folio">
<section id="p1">1</section>
<section id="p2">2</section>
<section id="p3">3</section>
<section id="p4">4</section>
</div>
Stacking the sections is straight forward:
/* ==== Our container ==== */
#folio {
position: relative;
...
}
section {
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
...
}
Non-permanent Viewing
Now all four sections are the same size as the parent div, and are all sitting on top of each other. But only one should be visible at a time, and when the view changes the visible element should zoom past and fade out while the next one appears.
To handle the visibility I opted to set opacity: 0 for all of the section elements. So, when you first load the demo page you just see an empty box.
The visibility (via opacity) of the elements is handled via the :target pseudo-class:
section:target {
opacity: 1;
}
So anytime a section becomes the target of a link, it pops back into view. To handle the reverse state — any section not the target of a link — I turned naturally to the :not() pseudo-class. (It’s OK if that isn’t natural to you; this is the first time I’ve actually found a use for the selector.)
section:not(:target) {
opacity: 0;
...
}
Moving Pictures
But we don’t want to just change opacity from 0 to 1 and back again with some funky pseudo-classes. The goal was to have the current section fly out past the view to be replaced with the next one. Time to turn to CSS3 Transitions and Transforms.
To set up the timing function, I originally had:
section {
...
transition: all 1s;
This tells the browser to transition all CSS properties from one state to the next over a 1 second period. For example, we could set section {color: blue;} and section:hover {color: red;}. The color change would happen over a 1 second period when the element was hovered on.
To achieve the zoom effect, we can use transform: scale(5);; this will scale, or zoom, our element 5 times its original size. Combine that with our opacity change and we have:
section:not(:target) {
opacity: 0;
transform: scale(5);
}
But, with the transition on the section elements the zoom effect occurred in both directions; when an element was no longer a target, it scaled up and faded out, but the target element could be seen scaling down and fading in. While that was cool on its own, it wasn’t the affect I was going for.
The key to unlocking the one-way zoom effect was to separate the timing function. First I moved the transition the section selector to the section:not(:target) selector. Then I added a second timing function for the section:target element and set it to affect opacity only:
section:target {
opacity: 1;
transition: opacity 4s;
}
section:not(:target) {
opacity: 0;
transition: all 1s;
transform: scale(5);
}
The result: :target elements instantly scale to normal size, but slowly fades in, while the :not(:target) elements fade out as they scale up. (I set a slower time so it wasn’t jarring to have it instantly snap in before the out-going image was done.)
In the End
Take a look at the source code of the demo to see everything involved. The code above has been cleaned up for presentation — in reality to get all of the transition and transforms to work you’ll need to include the vendor prefixes (-moz-, -webkit-, -o-). You can also see some other CSS3 selectors in use, mainly :nth-of-type(), which was used to set the background images. Yes, I could have used ID selectors, but I didn’t want to. Also in play is background-size: cover to size the background images.