Sam Croft

A designer/developer on HTML, CSS, JavaScript and PhoneGap

Using JavaScript’s transitionend event to maintain event control when handling sequential CSS transitions in a PhoneGap app

  • 6 comments

Filed in: Cordova, javascript, jQuery, PhoneGap

It’s pretty simple to create animations with CSS3′s transition and animation properties. And the ability to hardware accelerate these effects gives you a lot of power to create a PhoneGap app UI with a native feel.

One issue I sometimes run into is firing an event after a transition has completed and/or chaining multiple transitions to occur one after another while keeping event control at the same time. I’m not talking about an overly complex series of animations, I just mean dealing with a couple of a transitions that occur one after another. There are two methods that I use in these situations.

CSS transitions or animations?

CSS supports two types of animation achieved by using either the transition or animation properties. For this article I’m going to use the transition property as it’s better suited to straightforward UI element animations like moving something from a to b. The animation property on the otherhand is better used for more complex animations where there is more than just a start and end value.

Why transition event control is important

If your PhoneGap app makes use of CSS transitions you will likely encounter situations where you require event control when a transition completes. You might want to make an ajax request after your animation has finished, or more likely you just want to debug your app—being able to place some console logs throughout is always useful.

Using sequential CSS transitions

A common example where event control is useful would be the animation of an app titlebar, that contains a title and button, moving from out of view into view. You might just move the titlebar and its child elements into view all in one go. You could, however, create independent transitions for the view and its child elements for greater control. I’ll use the latter approach as an example, involving three separate transitions, one after another:

  1. Move the titlebar from the left into view
  2. Move the title from the top into view
  3. Fade the opacity of the button into view

The HTML and CSS

Some markup for a simple iOS style titlebar:

<div class="titlebar">
	<h1>Title</h1>
	<a href="#">Button</a>
</div>

And some CSS to style, position and prime the transitions:

.titlebar {
	background: #7822a3;
	position: relative;
	width: 320px;
	height: 44px;
	-webkit-transform: translate3d(-320px,0,0);
	-webkit-transition: -webkit-transform 0.4s ease-in;
}
	.titlebar.animate {
		-webkit-transform: translate3d(0,0,0);
	}
	.titlebar h1 {
		position: absolute;
		width: 320px;
		text-align: center;
		text-shadow: 0 1px rgba(0,0,0,0.6);
		color: #FFF;
		font-size: 20px;
		line-height: 44px;
		-webkit-transform: translate3d(0,-44px,0);
		-webkit-transition: -webkit-transform 0.2s 0.4s ease-in;
	}
		.titlebar.animate h1 {
			-webkit-transform: translate3d(0,0,0);
		}
	.titlebar a {
		position: absolute;
		top: 0;
		right: 0;
		width: 70px;
		height: 44px;
		background: #4d4ea3;
		text-align: center;
		text-decoration: none;
		color: #FFF;
		font-size: 14px;
		line-height: 44px;
		opacity: 0;
		-webkit-transition: opacity 0.2s 0.6s ease-in;
	}
		.titlebar.animate a {
			opacity: 1;
		}

Quite a bit of CSS here, but really only a few lines are necessary for the technique, the rest is purely how it looks and where it is positioned. Initially everything is going to be out of view. When the class, animate, is added to the titlebar element the animations will play out one after another.

For the titlebar I am going to move it into view using the -webkit-transform property to change its x position and set a duration of 0.4 seconds. I’m using translate3d here to ensure the animation is hardware accelerated on mobile Safari/Chrome.

With the title for the titlebar I’m again using the -webkit-transform property, but this time changing its y position with a duration of 0.2 seconds. To ensure this animation occurs after the titlebar is in position I’m also going to add a delay value (the second numeric value) of 0.4 seconds – the same value as the titlebar’s animation duration.

For the button I’m pretty much doing the same thing, but this time with the opacity property to fade it into view. When it comes to the delay value I’m using 0.6 seconds (0.4 + 0.2) to allow for both the titlebar element and the title to complete their respective animations.

To maintain event control for these sequential animations there are two approaches that I have used in the past…

Method one: using JavaScript’s setTimeout method

It might be tempting to use JavaScript’s setTimeout method and take the transition duration, from each CSS transition, and use it as the millisecond delay parameter for the method to chain a series of synchronised events. Like so:

  1. Start the first CSS transition
  2. Nest three (for my example) setTimeout methods with the corresponding delay values

We can accomplish this with a bit of jQuery:

$('.titlebar').addClass('animate');
		
setTimeout(function(){
	console.log('titlebar animation ended');
			
	setTimeout(function(){
		console.log('title animation ended');
				
		setTimeout(function(){
			console.log('button animation ended');
		},200);
	},200);
},400);

View a demo of the setTimeout method for CSS transition event control

First I add the class of animate to the titlebar element. 400ms later this transition completes and the next setTimeout method can be setup, this time with a delay value of 200ms. Following this another setTimeout method is initialised with another delay value of 200ms.

For the most part this works pretty well but there are some considerations:

  • You need to adjust your animation duration in two places – your CSS and JavaScript
  • A browser, or device, could lag and cause the method to execute slightly slower than anticipated, knocking things out of synchronisation. You’re relying on your CSS and JavaScript at precisely the same speed. Something that won’t always happen.
  • It feels too hacky

I think the biggest issue for me is the last consideration. This approach, while likely to work just fine, is just wrong and too risky.

Method two: using the JavaScript transitionend event

Method two is a more optimised approach, using the JavasScript event – transitionend. The Mozilla Developer Network describes this event quite nicely:

The transitionend event is fired when a CSS transition has completed. In the case where a transition is removed before completion, such as if the transition-property is removed, then the event will not fire.

The transitionend event means that you can keep event control of all transitions in your app. Super useful! And pretty easy. One thing to note is that you should also use the vendor prefix of the CSS transition type that you are using, as well as the official event name. So for my example transitionend and webkitTransitionEnd.

So sticking with the above example:

$('.titlebar').addClass('animate').bind('transitionend webkitTransitionEnd', function(){
	console.log('titlebar animation ended');
			
	$('.titlebar h1').bind('transitionend webkitTransitionEnd', function(){
		console.log('h1 animation ended');
	});
				
	$('.titlebar a').bind('transitionend webkitTransitionEnd', function(){
		console.log('button animation ended');
	});
});

Instantly this looks a lot cleaner and makes sense when you glance at it. Gone are the scattered transition duration values, replaced with the lovely transitionend event, bound to each element that has its own transition property.

So while the end result is exactly the same as the setTimeout method you have a more robust and sensible solution to event control with sequential CSS transitions.

Optimising usage of the transitionend event

There are a couple of things to bear in mind with the transitionend event. Firstly the transitionend event will bubble up from any child selector that also has a transition. That’s a serious problem in this particular example as two child events both have their own transition, which would lead to five events being triggered here, instead of three. To stop the event from bubbling you can ensure that each time you bind the event you stop it from propagating:

$('.titlebar').addClass('animate').bind('transitionend webkitTransitionEnd', function(e){
	e.stopPropagation();
	console.log('titlebar animation ended');
			
	$('.titlebar h1').bind('transitionend webkitTransitionEnd', function(e){
		e.stopPropagation();
		console.log('h1 animation ended');
	});
				
	$('.titlebar a').bind('transitionend webkitTransitionEnd', function(e){
		e.stopPropagation();
		console.log('button animation ended');
	});
});

Here I’m using the stopPropogation method for each of the transitionend events.

The second issue is just something to bear in mind. Keep on top of what you bind this event to. If you’re going to animate these elements out of view again, then you’re going to have another three events fire. Consider unbinding the event using jQuery’s off method.

View a demo of the transitionend event method

I wrote this with the intention of it being a PhoneGap article, but that’s just where I have used the technique. It can be used anywhere of course, but my examples only use the -webkit vendor prefix so bear this in mind.

About the author

I'm Sam Croft - a thirtysomething designer/developer and co-founder of Running in the Halls Ltd—a web and app development studio in Huddersfield, UK. I was educated in graphic design and now specialise in front-end web and app development; my main passion being usability and accessibility. I strongly believe web apps (vs native) are the future and love developing for mobile using the wonderful PhoneGap.

I am a massive sports fan - Formula One in particular. I live in the Pennines with my beautiful wife, Alex. Occasionally I own a large scruffy beard.

I tweet about all of my interests - you should follow me. I also have a .

  • http://twitter.com/nicmulvaney Nic Mulvaney

    This is excellent Sam. Just been through a project and replaced all my timeouts. Feels much more reliable now. I also discovered using animationend for CSS animations too. Very useful, thanks!

  • http://samcroft.co.uk/ Sam Croft

    Thanks Nic, glad it helped! The bubbling issue had me scratching my head for a why when I was seeing events fired lots of times.

    I think animation has a couple of other events too, although I haven’t tried them – animationstart and animationiteration. Could be very useful, but I rarely use animation over transition.

  • harshendrak

    Hi Sam

    Thanks for the nice post. I have a question about handling pages inside app. Please help me here.

    I am developing IPhone app using phonegap+Webservice(PHP+Mysql). Default login page appears. After successful login user will get dashboard section with Search Product, Add new product, Send message ,Inbox button. Contact Us etc…
    I have created all the forms in side index.html. By default login form visible. After login I am hiding login form & enabling dashboard form. Based on user click i show respective section and hide the other sections.

    Is there any way I can create individual page for each action like login, dashboard , add product, send message ,contact us etc.

    If yes how to redirect to new page when certain action happened. For example if login is successful how can i open dashboard.html & so on.

    Thanks & Regards

    Harsha

  • Bachka

    Very fine work Sam. Thank you!

  • Michal

    Just discovered your blog and this – amongst other – useful post. Keep up the good writing, you have nice way of explaining things!

  • http://samcroft.co.uk/ Sam Croft

    Thanks, Michal!