Understanding CSS Transitions And Class Timing
UPDATE: @Ron, in the comments, pointed out that this demo does not work as expected in Chrome. Apparently Firefox (my dev environment of choice) and Chrome handle this case somewhat differently.
I've only just started to use CSS transitions; but, I already love them. They seem like a great way to augment the behavior of your application user interfaces (UI) in a mannor that is inexpensive, easy to implement, and safe for browsers that don't yet support cutting-edge CSS standards. One thing that keeps tripping me up, however, is timing. And I don't mean the duration of a transition - I mean when does the transition actually take affect? Once I add or remove a transition rule, do I have to allow time to pass before altering the properties that I want transitioned?
Most of the transitions that I've used in CSS involve the toggling of some property. And, since I try to avoid inline CSS as much as possible, toggling a CSS property requires the addition and subsequent removal of a given CSS class. So, in this context, timing will refer to the mutation of element classes in relation to transition classes.
In the following example, I have an fixed-position box. With one class, it's positioned on the left. With another class it's positioned on the right. And, with the addition of a "transition" class, it should hopefully transition smoothly from one position to the other.
The demo contains four ways to change the CSS properties on the fixed position box:
- Adding position and transition class at the same time.
- Removing position and transition class at the same time.
- Removing position class, then the transition class after a timeout.
- Removing position class, then the transition class after a forced repaint of the UI.
Each of these has a different outcome, as you can see in the video:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>
CSS Transitions And Class Timing
</title>
<style type="text/css">
div.box {
background-color: #FAFAFA ;
border: 1px solid #CCCCCC ;
height: 100px ;
left: 20px ;
line-height: 100px ;
position: fixed ;
text-align: center ;
top: 120px ;
width: 100px ;
}
div.moved {
left: 400px ;
}
div.transition {
transition: left 1s ease ;
-webkit-transition: left 1s ease ;
}
</style>
</head>
<body>
<h1>
CSS Transitions And Class Timing
</h1>
<p>
<a href="#" class="add">Add</a>
—
<a href="#" class="remove">Remove</a>
—
<a href="#" class="remove-delay">Remove Delay</a>
—
<a href="#" class="remove-redraw">Remove Redraw</a>
</p>
<div class="box">
I Am Box
</div>
<!-- Load jQuery from the CDN. -->
<script
type="text/javascript"
src="//code.jquery.com/jquery-1.9.1.min.js">
</script>
<script type="text/javascript">
var box = $( "div.box" );
// Add both classes at the same time.
$( "a.add" ).click(
function() {
box.addClass( "moved transition" );
}
);
// Remove both classes at the same time.
$( "a.remove" ).click(
function() {
box.removeClass( "moved transition" );
}
);
// Remove the transition class after delay.
$( "a.remove-delay" ).click(
function() {
box.removeClass( "moved" );
setTimeout(
function() {
box.removeClass( "transition" );
},
10
);
}
);
// Remove the transition class after forced repaint. I got
// this tip from Alex McCaw.
$( "a.remove-redraw" ).click(
function() {
box.removeClass( "moved" );
// Forces a repaint in most browsers (apparently).
var height = box[ 0 ].offsetHeight;
box.removeClass( "transition" );
}
);
</script>
</body>
</html>
From my experimentation, it seems that adding transition classes is a piece of cake. When you add the transition class and change the target properties in the same event loop tick, the transition takes effect immediately. Timing doesn't appear to be an issue.
The complexity starts to appear when you want to remove a transition class. From my experimentation, it seems that when you remove a transition class and change the target properties in the same event loop tick, the properties are updated without any transition. That is to say, they immediately go from one value to another without any intermediary steps.
To overcome this timing dilemma, we have two options:
- Remove the transition class after a short timeout.
- Remove the transition class after a forced repaint.
The timeout approach is fairly straightforward: you remove the transition class after the transition has already been triggered (and is currently taking place). The forced repaint, on the other hand, is something of a hack that requires you to really understand how browsers render their DOM trees.
I don't have a deep understanding of browser rendering. Fortunately, Alex McCaw has an excellent article on CSS transitions that showed me how to do this. Apparently, if you ask for a element's dimensions, the browser will force a repaint in order to calculate the element's offsets. At this point, the browser will initiate any CSS-based transitions, which allows us to remove the transition class without needing a timeout.
Most of the time, you probably won't have to worry about timing since you'll leave the transition rules in place. However, if you ever have to toggle the ability for an element to transition at all, it is important to understanding the timing conditions in which transitions take effect. Hopefully this helps!
Want to use code from this post? Check out the license.
Reader Comments
Here's how I load jQuery:
This get's version 2.0 unless you are using IE 8, in which case it gets the latest 1.x version from Google.
I am not really sure what is the desired outcome in this case but if the class that is applying transitioning is to be removed after the transition is complete I would listen for the transition complete event, otherwise the behaviour seems erratic and hard to track.
"Remove Delay" doesn't work in Chrome 25 nor Firefox 18 or 19. "Remove Redraw" does work for FF 18 and 19 bit not in Chrome
@Phillip,
Looks cool. I haven't played around much with the v2.x stuff yet.
@Ron,
I think it has to do with the duration of the timeout. I found that if I dropped the timeout below 10ms, then Firefox would *sometimes* work and sometimes not work. I would guess that if the timeout was bumped up to 50ms, then it would work across all the browsers. It might have something to do with render optimizations and when the browser actually has a chance to repaint?? Not exactly sure.
@Peter,
The point of the post was no so much to figure out a way to add or remove a transition; more so, it was just to try and understand when the transition takes place AND how you can make interact with your CSS around the time of a transition initiation.
I think the example is a little funky because I am explicitly adding a class that implements transition. But, imagine that you have a base class that implements transition; then, you have a cascading class that overrides the transition:
.base { ... transition: left 1s ease }
.full-width { ... transition: none ; }
In this case, we have a "full width" class that is meant to override the transition, ideally in *both* directions. That is, when I toggle into and out of "full width mode", I don't want any transitions. But, will it work as intended???
@Peter,
Another use case would be modal windows. Image that you are using something like Twitter Bootstrap and you have modal windows that *fade* in and fade out.
Now, image that you currently have one modal window open and it has code that needs to trigger a different modal window. In such a case, you don't want the *fade* effect to be in place during the modal-to-modal transition. The best user experience is probably something like this:
* Modal A fades in.
* Users clicks link in modal to open Modal B.
* Modal A hides instantly.
* Modal B shows instantly.
* User clicks to close modal B.
* Modal B fades out.
Notice that the fade was still in effect for the *hiding* of modal B. But, it was effectively removed for the hiding of modal A and the subsequent showing of modal B. This is where it can really be important to understand the timing and conditions in which CSS animations actually take effect.
@Ben,
You're right, increasing the timeout worked for FF. Chrome (25) at least seems to animate until the timeout occurs. SO that is to say that the return animation works in Chrome if your timeout >= the transition time in your CSS.
I can imagine having fun with this and mutation events on a larger scale. Checking for when a className changes to your transition class then do the settimeout.
I also wonder about your example but use requestAnimationFrame instead of setinterval, as that might seem like a good blending of the settimeout and repaint options
I don't quite get what the issue here is. If you want to wait until the transition is over before you change something, why not subscribe to the transitionend event? Each CSS transition can be reacted to with JS - no need for any jQuery or timeout hacks.
I might read this wrong though.
@All,
Based on some of your feedback, it seems that maybe my confusing is just a matter of my newness to CSS transitions and a bit of ignorance as to how it all works. I've tried to put together another quick demo to showcase why the timing is so important to understand:
www.bennadel.com/blog/2462-Understanding-CSS-Transitions-And-Class-Timing-Revisited-.htm
It sounds like people just "get" this stuff; and now, I'm also one of those people... it just took me a little while :)
@Ron,
Ah, I see it now - I didn't realize that Chrome would have a different behavior. It looks like it [Chrome] cancels the transition if the transition behavior is removed mid-transition.
Thanks for pointing that out! I do most of my development work in Firefox / Firebug. Yo, this stuff is really confusing :D
@Ron,
Thanks - I've added a note to the top of the blog post indicating the difference of behavior in Firefox / Chrome.
Thanks Ben,
Exactly what i was looking for. nice hack. In my case i need to apply two class name (one with animation other without animation).
thanks
@Ashraf,
Glad this was helpful. I find the CSS transition stuff a bit confusing. Trying to help clear up some concepts.