1

I have a jQuery plugin that moves an element on the screen, but has an "animate" toggle to show a slide transition or not. In attempting to use CSS Transitions rather than Javascript transitions for the change, I ran across this, and I'm not sure if it's a bug/quirk, or I'm just doing it wrong:

var $item = $('#myItem');
if (!animate) {
  $item.removeClass('csstransitions'); // Class that has "transition:left 0.25s ease-out" 
  $('#myItem').css('left', '300px'); // Move the element
  $('#myItem').addClass('csstransitions'); // Re-apply transitions class
}

When done this way, where the css change happens while the transitions class is not applied to the element, but is applied immediately after, some browsers (Chrome and Safari, in my testing) still apply the CSS transition, when by my logic, it should just snap to the new location.

See this in action in this jsFiddle; In Chrome or Safari, click the "No Delay" button, and see that it does still animate the position of the box, while the "Delay" button (which uses a timeout set for one millisecond later) doesn't animate the CSS change.

As indicated in the jsFiddle, I'm having to use a setTimeout call (setTimeout(function() { $el.addClass('csstransition'); }, 1);) to get the proper behavior in Chrome and Safari. Is this just because CSS transitions are bleeding edge, or am I doing something wrong, and there's a simpler way to temporarily "turn of" the transitions?

EDIT: Noticed this question is similar, and while the answer on that one is to just separate the two calls, the question remains of "why do we (web developers) need to separate those two calls?" Is this the method we should be using to toggle CSS transitions?

Community
  • 1
  • 1
MidnightLightning
  • 6,715
  • 5
  • 44
  • 68

1 Answers1

2

I would vote for quirk, or implementation difference.

Before transitions, it really didn't matter which order styles were applied, because in practicality, order didn't matter, just specificity. But with transitions, an element of time delay was added into styles, which is the crux of the issue.

Not knowing how any of the browsers apply styles, I could guess that Safari and Chrome do some optimizations to not have to re-flow the page after every style update. Instead, they probably wait for particular intervals or events to do the updates, such as at the end of code blocks.

Some of the differences are detailed here:
http://taligarsiel.com/Projects/howbrowserswork1.htm

Although, I don't know if this specific issue is covered.

As demonstration, another way you could handle this is to have 2 click handlers:

$('button#nodelay').on('click', function() {
    var $el = $('#square');
    $el.removeClass('csstransition');
    $el.css('left', '100px');
}).on('click', function() {
    $el.addClass('csstransition');
});

This basically divides the two updates into separate code blocks, much like the setTimeout method.

Also, as transitions are still draft standard, I wouldn't depend on any of this behavior to stay consistent, and there are quirks. (I ran into an issue where transitioning left and top at the same time didn't work in all browsers).

Edit

To further explain, if the browser renders all CSS as soon as it is added to the DOM you get this flow:

  1. CSS left: 300px added to DOM element
  2. Style rendered: Browser checks to see if there is a transition on the element. If so, animate, if not, apply immediately. In this case, there is not a transition (yet), so no animation occurs.
  3. CSS transition: left 2s ease-out added to DOM element
  4. Style rendered: No rendering change, transition applies to future left changes.

However, if the browser optimizes the rendering of CSS, by grouping at the end of code blocks (or something of the sort), you get this:

  1. CSS left: 300px added to DOM element
  2. CSS transition: left 2s ease-out added to DOM element
  3. Rendering point reached (end of code block, etc), all styles rendered:
  4. When left is applied, the browser checks to see if there is a transition on the element. If so, animate, if not, apply immediately. In this case, there is a transition, so the animation occurs.

So, the length of the animation, and the time you wait is irrelevant. What is important is that the left: 300px gets rendered before the transition is applied. Currently, in WebKit, this means applying the transition style in a separate, later code block than the left style. This is accomplished by all of the answers suggested setTimeout (event with 0 delay), separate click handler (if applied second), or a function callback.

Here is another way that works:

$('button#nodelay').on('click', function() {
    var $el = $('#square');
    $el.removeClass('csstransition');
    $el.css('left', '100px').css('left'); // <-- This (or .show() even)
    $el.addClass('csstransition');
});

This works, because you are forcing the browser to stop and evaluate the CSS so that you can get the value of left (although you could put any valid css attribute in the second .css() call). In this case, it applies all of the CSS and forces the element(s) to re-render.

Jeff B
  • 29,943
  • 7
  • 61
  • 90
  • why not use the callback function? Two click handlers seems horribly redundant. – RestingRobot Mar 19 '12 at 18:49
  • Sure. I was demonstrating the point that the key is two code blocks. He already has a valid solution to his problem with `setTimeout`. Pick the one that is most efficient and least obfuscated. – Jeff B Mar 19 '12 at 18:52
  • set timeout doesn't seem like a valid solution as it is dependent on the animation only taking a set amount of time. If you set the timeout to low, your back at square one. A callback is meant for this type of situation. Also, to answer your question, Chrome and Safari, (specifically webkit browsers), use what is called an animation frame, that can be set or dependant upon ones hardware. Old source: http://blog.chromium.org/2011/03/getting-smoother-animated-web-content.html – RestingRobot Mar 19 '12 at 18:57
  • Not true, the `setTimeout` can be 0. All that matters is that the code block is different. If the transition is applied *after* the `left` css change, the animation will not occur. The time of the animation is irrelevant. – Jeff B Mar 19 '12 at 19:02
  • Ok, I see your point. My explanation is really simplistic, however, the callback is still the most elegant/intuitive method. The problem has to do with the order in which the calls are executed, using a callback ensures that the addClass will not be called until the animation finishes. – RestingRobot Mar 19 '12 at 19:15
  • http://stackoverflow.com/questions/779379/why-does-settimeoutfn-0-sometimes-help This is why even with a zero time, the class is not added – RestingRobot Mar 19 '12 at 19:17