Using transitionend
event to detect
when a transition has finished
A likely part of many complex CSS animations involves doing something after
one transition has ended. Estimating when this has occurred- as we've done
above using a setTimeout()
function on the previous page- is both unreliable and
unmanageable, especially if we wish to keep track of multiple transitions.
This is where the transitionend
event comes in- a JavaScript event that
fires whenever, well, you've guessed it, a transition has ended. This event
is supported in IE10+ and modern versions of other browsers, including on
the mobile front.
transitionend
can be attached to an element like any other in jQuery,
with a couple of caveats:
$(element).on('transitionend
webkitTransitionEnd', function(e){
// access original Event object versus jQuery's normalized version
var evt = e.originalEvent
})
Firstly, notice the presence of "webkitTransitionEnd
" in
addition to the standard "transitionend
" event when hooking up
with the event handler. This is necessary in order for Safari (as of Safari
v5.1.7 and jQuery 2.0) to properly recognize the event, despite the fact
that jQuery 1.8+ automatically normalizes the majority of other CSS3 property and event
names. In this case, it seems a little additional nudge in the right
direction is necessary. Secondly, inside the event function, instead of the
typical route of accessing jQuery's normalized version of the event object,
or e
, you'll want to instead reference the untouched, original
Event object, which jQuery thoughtfully makes available via
e.originalEvent
. This is because, as of jQuery 2.0, most of the
properties associated with the original Event object during the "transitionend
"
event have yet to be ported over to the normalized version's, so we must
rely on the former instead.
The transitionend
event bubbles, which is good news, as it means we can
define our event handler function higher up in the chain of nodes from the
elements firing the event, and handle them all easily without having to
define multiple transitionend
event handlers.
When a transitionend
event fires, the Event object is populated with the
following properties:
Property | Description |
---|---|
target | The event target; in other words, the element that
actually fired this transitionend event. |
type | The type of event, in this case, the string "transitionend". |
canBubble | Boolean indicating whether this event bubbles. |
cancelable | Boolean indicating whether it's possible to cancel this event. |
propertyName | The name of the CSS property being transitioned. In a
CSS transition where there are multiple CSS properties being
transitioned, transitionend fires once for each of
these properties, one after another. |
elapsedTime | The amount of time this transition has been running when
the event (in this case, transitionend ) fires, in seconds. |
pseudoElement | The name of the CSS pseudo-element (beginning with two colons, such as ":before ",
":after ", ":first-letter " etc) on which the transition occurred, or the empty string if
the transition occurred on the element itself. |
One of the most important points to remember here is that transitionend
fires once for each transitioned property within the associated element, and
not just once for the entire element. In the case of the below CSS selector
for example:
h3:hover{
font-size: 120%;
color: red;
background: black;
-moz-transition: all 0.3s ease-in;
-webkit-transition: all 0.3s ease-in;
-ms-transition: all 0.3s ease-in;
transition: all 0.3s ease-in;
}
When you hover over a H3 element to trigger the transition effect, you'll
find that the transitionend
event for the element will have fired at least 3
times, once for "font-size
", once for "color
", and once for "background
".
Why at a minimum? Because depending on the browser, other related CSS
properties may also be transitioned indirectly as a result, such as "margin-top
"
and "margin-bottom
" due to the font size being changed. The
transitionend
event will also fire once for each of these properties as
well, which you can see exactly what by examining the propertyName
property of the Event object. The bottom line is, transitionend
fires not
once on an element each time there is a transition occurring on it, but once
for every property that has transitioned on the element.
Refactoring our dropping text example to use transitionend
The best way to see how to use transitionend
is to see it in action, and
we have the perfect candidate to operate on, our
previous dropping text
example. Recall that it used a crude technique involving setTimeout()
to
estimate when the transition has finished to reset it:
<script>
jQuery(function(){
var $header2 = $('#header2')
$header2.lettering() // wrap <span class="charx"/ > around each character
within header
var $spans = $header2.find('span')
var delay = 0
$header2.on('click', function(){
$spans.each(function(){
$(this).css({transitionDelay: delay+'s'}) // apply sequential trans delay
to each character
delay += 0.1
})
$header2.addClass('dropped') // Add "dropped" class to header to apply
transition
setTimeout(function(){ // reset header code
$spans.each(function(){
$(this).css({transitionDelay: '0ms'}) // set transition delay to 0
so when 'dropped' class is removed, letter appears instantly
})
$header2.removeClass('dropped') // remove class at the "end" to reset
header.
delay = 0
}, 1800) // 1800 is just rough estimate of time transition will finish,
not the best way
})
})
</script>
<body>
<h3 id="header2" class="header">Click on me!</h3>
</body>
Lets bid farewell to the code in red and utilize transitionend
instead to
accomplish the following:
- Keep track of precisely when the transition on each letter has
ended, and reset the
transition-delay
value on that letter to 0. - Keep a tally of how many transitions have ended, and when it matches
the total number of letters, reset the entire animation by removing the
"
dropped
" class from the parent container.
Demo:
Click on me!
Here's the improved version:
<style>
#header2{
font-family: 'Orbitron', sans-serif; /* font style. Default uses Google
fonts */
text-shadow: 2px 2px #B4EAF3, 3px 3px #B4EAF3, 4px 4px #B4EAF3, 5px 5px
#B4EAF3, 6px 6px #B4EAF3;
font-size: 64px; /* font size of text */
color: #207688;
letter-spacing: 15px;
font-weight: 800;
text-transform: uppercase;
position: relative;
}
#header2 span{
display: inline-block;
}
.dropped span{
-moz-transform: translateY(300px) rotateZ(120deg); /* rotate, translate, and
disappear */
-webkit-transform: translateY(300px) rotateZ(120deg);
transform: translateY(300px) rotateZ(120deg);
opacity: 0;
-moz-transition: all 0.3s ease-in;
-webkit-transition: all 0.3s ease-in;
-ms-transition: all 0.3s ease-in;
transition: all 0.3s ease-in;
}
</style>
<script>
jQuery(function(){
var $header2 = $('#header2')
$header2.lettering() // wrap <span class="charx"/ > around each character
within header
var $spans = $header2.find('span')
var delay = 0
var transitionsfired = 0
var transitionisrunning = false
$header2.on('transitionend webkitTransitionEnd',
function(e){
var $target = $(e.target) // target letter transitionend fired on
if (/transform/i.test(e.originalEvent.propertyName)){ // check event
fired on "transform" prop
transitionsfired += 1
$target.css({transitionDelay:'0ms'}) // set transition delay to 0
so when 'dropped' class is removed, letter appears instantly
if (transitionsfired == $spans.length){ // all transitions on
characters have completed?
transitionsfired = 0 // reset number of transitions fired
delay = 0
$header2.removeClass('dropped')
transitionisrunning = false // indicate transition has
stopped
}
}
})
$header2.on('click', function(){
if (transitionisrunning === false){
$spans.each(function(){
$(this).css({transitionDelay: delay+'s'}) // apply sequential
trans delay to each character
delay += 0.1
})
$header2.addClass('dropped') // Add "dropped" class to header to
apply transition
transitionisrunning = true // indicate transition is running
}
})
})
</script>
<h3 id="header2" class="header">Click on me!</h3>
The demo may look identical to its predecessor on the previous page in terms of behaviour, but it's vastly more reliable and versatile. No longer are we guessing when each transition has ended, which when it comes to coding is never a good thing (animation prematurely cutting off, longer than necessary delay before reset, the end of the World etc). Lets examine the innards of the above script to see how everything comes together:
- Near the end of the code, we add the class "
dropped
" to the header, kicking off the CSS3 transition. - We attach the
transitionend
event to the H2 container of our text, so it monitors transitions that occur on all the individual characters inside it (since the event bubbles). - We reference the target character the
transitionend
actually fired on (each character is wrapped in its own span tag) withe.target
before making it a jQuery object; in this case it's not necessary to usee.originalEvent.target
, as the former isn't atransitionend
specific property that jQuery has yet to properly normalize. - Each time
transitionend
fires, we pick one of the properties being transitioned on the target- in this casetransform
- to tell us the transition has completed in general over the target. We could have gone with any other property inside the "dropped
" class- properties that are part of the transition. Recall thattransitionend
fires multiple times on an element based on the number of properties there are that are being transitioned. By picking one of these properties to see when its transition has ended, we essentially know when the entire transition on the corresponding target character has ended. When that happens, we increment thetransitionsfired
variable by one and set the character'stransition-delay
property to 0 (so there's no animation when the "dropped
" class is removed at the end). - We compare the variable
transitionsfired
against the number of characters in the header- if they match, that means the transitions on all of the characters have completed. When that happens, we reset some variables that help keep tabs on various aspects of the transitions, and just as importantly, remove the "dropped
" class from the header, resetting the entire animation in the process.
The role of JavaScript when it comes to CSS3 transitions may be a supporting one, but depending on the scene, a vital one at the same time. With the ability to dynamically set CSS3 properties and respond to a transition ending, the stage is set for more intricate CSS3 effects!
- Manipulating CSS3 transitions using jQuery
- Using
transitionend
event to detect when a transition has finished
End of Tutorial