Getting CSS animations to trigger at the right time

  • Knowledge needed: Intermediate CSS, HTML, JavaScript
  • Requires: jQuery, Modernizr
  • Project time: 1 hour

Support file 1

When we redeveloped our new website, we decided to use CSS3 animations to provide an impressive and interactive site experience. However, we struggled to time the animations to trigger at the right time.

Because animated elements "below the fold" (depending on the screen resolution) animate on page load, some animations had already played before the user had chance to scroll to them.

3magine’s homepage. The slider animations start immediately after preloading, but only if they are visible

To prevent this, we delay the animations until the user can see them. The animations trigger as the user scrolls down and the elements come into view, which creates a staggered effect. To do this we used specific classes for CSS3 Transitions and some JavaScript to trigger the correct elements during scrolling.

When the page first loads, the elements “below” the fold do not animate until scrolled

This is supported on any modern browser with CSS3 Transitions support.

Here’s the loading code we placed at the end of the <head> element.

<script src=""></script>
<script src=""></script>

if (Modernizr.csstransitions) {
function preloadImages(imgs, callback) {
var cache = [],
imgsTotal = imgs.length,
imgsLoaded = 0;

$(imgs).each(function (i, img) {
var cacheImage = document.createElement('img');
cacheImage.onload = function () {
if (++imgsLoaded == imgsTotal) callback();
cacheImage.src = $(img).attr('src');
$.fn.trans = function () {
var t = arguments[0],
d = arguments[1] || '';
if (t) {
$.each(this, function (i, e) {
$(['-webkit-', '-moz-', '-o-', '-ms-', '']).each(function (i, p) {
$(e).css(p + 'transition' + d, t);

document.write('<link rel="stylesheet" href="animations.css" />');

//preload images contained within elements that need to animate
preloadImages($('.services img, .featured img'), function () {
$('.services, .featured').appear({
once: true,
forEachVisible: function (i, e) {
$(e).data('delay', i);
appear: function () {
var delay = 150,
stagger = 800,
sequential_delay = stagger * parseInt($(this).data('delay')) || 0;

$(this).children().each(function (i, e) {
$(e).trans(i * delay + sequential_delay + 'ms', '-delay');

First, include all necessary JavaScript libraries, such as Modernizr, jQuery and a custom jQuery Appear script. We use Modernizr to test if the browser supports CSS3 Transitions. If so, the script executes. If not, the page loads as normal without animations taking place.

The JavaScript automatically calculated a sequential delay when multiple animated sections are simultaneously visible

The jQuery Appear plugin was originally written by Michael Hixon and modified by us specifically for our website. Get our modified version here.

If the browser supports CSS3 transitions we define two helper functions: preloadImages and trans jQuery extension.

Below is the CSS code to animate this, which goes into animations.css. We use custom cubic-bezier easing as it looks better than the standard linear or default ease-in/out functions in CSS3. CSS easing animation tool, Ceaser, will help you design your own easing functions.

.services.animationBegin > * {
-webkit-transition:all 0 linear 0 !important; /* rewind instantly */
-moz-transition:all 0 linear 0 !important;
-o-transition:all 0 linear 0 !important;
-ms-transition:all 0 linear 0 !important;
transition:all 0 linear 0 !important;

.featured > * {
-webkit-transition: all 800ms cubic-bezier(0.230, 1.000, 0.320, 1.000) 0ms;
-moz-transition: all 800ms cubic-bezier(0.230, 1.000, 0.320, 1.000) 0ms;
-o-transition: all 800ms cubic-bezier(0.230, 1.000, 0.320, 1.000) 0ms;
-ms-transition: all 800ms cubic-bezier(0.230, 1.000, 0.320, 1.000) 0ms;
transition: all 800ms cubic-bezier(0.230, 1.000, 0.320, 1.000) 0ms;
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transition: rotate(0deg);

.featured.animationBegin > * {
-webkit-transition:all 0 linear 0 !important; /* rewind instantly */
-moz-transition:all 0 linear 0 !important;
-o-transition:all 0 linear 0 !important;
-ms-transition:all 0 linear 0 !important;
transition:all 0 linear 0 !important;

-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-o-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transition: rotate(45deg);

Why document.write? For browsers that lack CSS transitions support, CSS must be injected so the animation loads at the top of the page immediately after Modernizr tests support. We were reluctant to do anything IE-specific (for example, conditional comments) or try user agent sniffing because a lot of older versions of browsers, such as IE, display the layout fine but lack support for Transitions (and that’s what Modernizr is for).

Document.write works cross-browser, including older versions of Opera. It's fast enough to prevent a ‘flash of unstyled content’ and display visible elements before the animations begin.

This sets up two things: the first section of code, .services > * dictates where we want the animation to end up. A similar selector, but with an .animationBegin class, dictates where the animation starts. Note that the parent element of the items that need to be animated actually starts with this class in its markup (shown above).

The next few lines of code are executed when the entire document has loaded. The preloadImages function targets the images within the elements that will be animated and preloads them.

This prevents animations loading before the target images are displayed in the browser. When all the images are loaded we call our jQuery Appear plugin, which does two things: first, it scans the currently visible elements on the page and sets a delay parameter on all the planned animated elements. Second, it attaches an appear event that triggers for all visible elements.

This controls the staggering and delay given to each child element that’s to be animated. We’re using a conservative 150ms here, but, please, play around with this number to find the right delay for your website by changing the delay variable. Each successive child has this delay multiplied against the index of the each function: the first delay is 0, the second delay 150, the 3rd delay 300 and so on.

Finally, and perhaps the most important bit, is that the parent UI’s animationBegin class is removed, which starts the animations.

Here’s the HTML content we're animating using they CSS/JavaScript code explained above.

<ul class="services animationBegin">
<li class="imac-small">
<h2><a href="">WordPress &amp; CMS</a></h2>
<a href=""><span class="device"><img width="162" height="92" src="" class="attachment-post-thumbnail wp-post-image" alt="tiny" title="tiny" /></span></a>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry.</p>
<li class="air-small">
<h2><a href="">UI &amp; UX Design</a></h2>
<a href=""><span class="device"><img width="116" height="73" src="" class="attachment-post-thumbnail wp-post-image" alt="tiny" title="tiny" /></span></a>
<p>Lorem Ipsum has been the industry's standard dummy text ever since the 1500s...</p>
<li class="ipad-small">
<h2><a href="">Web Applications</a></h2>
<a href=""><span class="device"><img width="88" height="118" src="" class="attachment-post-thumbnail wp-post-image" alt="web-applications-sample" title="web-applications-sample" /></span></a>
<p>...when an unknown printer took a galley of type and scrambled it to make a type specimen book.</p>
<li class="pro-small last">
<h2><a href="">Social Networks</a></h2>
<a href=""><span class="device"><img width="121" height="76" src="" class="attachment-post-thumbnail wp-post-image" alt="tiny" title="tiny" /></span></a>
<p>It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.</p>

<ul class="featured animationBegin">
<li><img src="" alt="Featured on"></li>
<li><img src="" alt="CBC Television"></li>
<li><img src="" alt="Techvibes"></li>
<li><img src="" alt="Daytime Toronto"></li>
<li><img src="" alt="Dragon's Den"></li>
<li class="last"><img src="" alt="Z103.5"></li>

This is really straightforward markup. In this case, it’s a list of services and then a “featured-on” list of logos.

‘Pinch’ the window and scroll down to see the animations delay

Click the link at the top of this page to see a working demo. The animated elements start off-screen and, when you scroll into view, become triggered.

Karl Schellenberg, a lead programmer at 3magine, has over 10 years of experience developing for the web. His philosophy is that no problem is too difficult. Outside of his work Karl’s computer-related hobbies include 3D rendering, animation and gaming.

Liked this? Read these!