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.

Create A Random Ken Burns Effect For Images With CSS & JavaScript

Ashokan Farewell optional.

Of late AskMen.com has featured some interesting and innovative web design techniques. One of the most notable is a “Ken Burns” technique employed on header images: a subtle combination of zoom and pan that is both engaging and unpredictable, choosing random locations in the image to slowly focus on over time.

A few of my students have been asking how they might achieve this in their own work. While I haven’t looked at the code on the AskMen site, I’m happy to share how I would pull it off, using a little JavaScript to generate a random CSS animation.

First, we need to set up the image:

<figure id="burnsbox">
<img src="janelle-monae.jpg" alt="Photograph of Janelle Monae in concert, shot in silhouette against a blue light">
</figure>

The CSS use a variation of the technique I’ve covered recently: a relatively-positioned box containing absolutely-positioned responsive content.

figure#burnsbox { overflow: hidden; position: relative;
padding-top: 60%;  }
figure#burnsbox img { position: absolute; 
top: 0; left:-5%; width: 110%; height: 110%; }

Note that the image is made slightly larger that its container, overlapping by 5% on each side:

This overlap (shown in orange) is hidden by the overflow: hidden on the container (shown as a black border). The padding-bottom on the figure element preserves the correct height by using a value that matches the aspect ratio of the image.

Setting Expectations

To make the effect work we need to do several things:

For this example, let’s assume the following:

Creating A Random CSS Animation

Under those conditions, generating the initial random numbers is easy:

function randomizer(min,max) {
randomresult = Math.random() * (max - min) + min;
return randomresult;
}
var maxscale = 1.4;
var minscale = 1.1;
var minMov = 5;
var maxMov = 10;
var scalar = randomizer(minscale,maxscale).toFixed(2);
var moveX = randomizer(minMov,maxMov).toFixed(2);
moveX = Math.random() < 0.5 ? -Math.abs(moveX) : Math.abs(moveX);
var moveY = randomizer(minMov,maxMov).toFixed(2);
moveY = Math.random() < 0.5 ? -Math.abs(moveY) : Math.abs(moveY);

I’ve used toFixed just to avoid the unnecessary complication of trying to scale to a number like 1.269274802; the line of code after the determination of the value of moveX and moveY randomly switches the result between positive and negative.

Next, we need to look for an existing embedded style sheet in the document:

var lastSheet = document.styleSheets[document.styleSheets.length - 1];

Since we don’t want to write out the animation code more than once, we’ll need to determine if the browser requires prefixes. In this case, I’ll use the CSSOM:

var prefix = "";
if (CSSRule.WEBKIT_KEYFRAMES_RULE) { prefix = "-webkit-"; }
else if (CSSRule.MOZ_KEYFRAMES_RULE) { prefix = "-moz-"; }

Finally, we need to create the animation sequence, which will pan and zoom the image for 80% of the animation time, holding it at both the start and beginning for 10% of the total duration. The animation will be added to the existing stylesheet using insertRule, placing it at the very start of the CSS (i.e. index position 0).

lastSheet.insertRule("@"+prefix+"keyframes zoomzoom {" + 
"10% { " + prefix + "transform: scale(1); } " + 
"90% { " + prefix + "transform: scale(" + scalar +" ) translate(" + moveX +"%," +  moveY + "%); } " +
"100% { " + prefix + "transform: scale(" + scalar + ") translate(" + moveX +"%," +  moveY +"%); } }", 0);

(I’ve added more concatenation than absolutely necessary to break up the lines for increased readability).

If the script was used in an up-to-date version of Firefox, the randomized result might look something like this:

@keyframes zoomzoom {
10% { transform: scale(1); }
90% { transform: scale(1.34) translate(7.21%, 5.89%); }
100% { transform: scale(1.34) translate(7.21%, 5.89%); }
}

The final step is to control the image with the generated keyframe animation sequence. We could do that with more JavaScript, or just by adding the declarations to the existing styles:

figure#burnsbox img { position: absolute; top: 0; left:-5%; 
width: 110%; height: 110%;
-moz-animation: zoomzoom 12s linear alternate infinite;
-webkit-animation: zoomzoom 12s linear alternate infinite;
animation: zoomzoom 12s linear alternate infinite; }

An alternative and slightly safer approach is to create the new stylesheet only when the page has completed loading, pushing the appropriate element towards the newly-formed animation. The rest of the code remains unchanged:

window.onload = function(){
sheet = document.createElement('style');
document.head.appendChild(sheet);
var anim = "@"+prefix+"keyframes burnseffect { 10% { " + prefix + "transform: scale(1); } 90% { " + prefix + "transform: scale(" + scalar +" ) translate(" + moveX +"%," +  moveY + "%); } 100% { " + prefix + "transform: scale(" + scalar + ") translate(" + moveX +"%," +  moveY +"%); } }";
sheet.appendChild(document.createTextNode(anim));
document.head.appendChild(sheet);
monae = document.querySelector("figure#burnsbox img");
monae.style.webkitAnimationName = 'burnseffect';
monae.style.mozAnimationName = 'burnseffect';
monae.style.animationName = 'burnseffect';
}

Of course you can also control any other aspect of CSS animation with JavaScript, including duration and behaviour, all of which I will cover in future blog articles.

The result is what you can see above: a randomized, repeating pan-and-zoom sequence for the photograph of Janelle Monáe in concert, taken by Nastassia Davis and licensed under Creative Commons. Play with this 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.