25

I'm creating a DOM element (a div), adding it to the DOM, then changing its width all in one quick hit in javascript. This in theory should trigger a CSS3 transition, but the result is straight from A to B, without the transition in between.

If I make the width change through a separate test click event everything works as expected.

Here's my JS and CSS:

JS (jQuery):

var div = $('<div />').addClass('trans').css('width', '20px');
$('#container').append(div);
div.css('width', '200px');

CSS (just mozilla for the minute):

.trans {
    -moz-transition-property: all;
    -moz-transition-duration: 5s;
    height: 20px;
    background-color: cyan;
}

Am I messing up here, or is the "all in one quick hit" not the way things should be done?

All help is really appreciated.

Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308
Kong
  • 8,792
  • 15
  • 68
  • 98
  • I'm confused - do you want to animate the height or the width? or both? Also, is using jquery's .animate() method out of the question? Because if it's not, that's the way I'd recommend to do it. Let me know, and I'll write you up a simple way to do it with jQuery (or fix the CSS). – Connor Aug 15 '11 at 19:29
  • @onetrickpony – It's very unclear what additional information you're looking for with this bounty. Could you please elaborate in a comment? – Josh Burgess Dec 10 '14 at 16:03
  • I'm just wondering if there's a better way than using timeouts to make the browser update those values @Josh – nice ass Dec 10 '14 at 17:03
  • @onetrickpony - Other than the answer that was accepted? Because that's _probably_ the best way. I guess what I'm confused about is the scenario where you wouldn't know which property was transitioned. – Josh Burgess Dec 10 '14 at 17:04
  • It's for when you have the transitions defined in the CSS file, and don't want to tell the js about them, which should only worry about appending the html and changing the class – nice ass Dec 10 '14 at 17:09
  • @onetrickpony - Ah, gotcha. Okay, what you need is a reflow, then. I'll add an answer shortly. – Josh Burgess Dec 11 '14 at 17:28

6 Answers6

48

A cleaner approach that does not rely on setTimeout, is to read the css property in question before setting it:

var div = $('<div />').addClass('trans');
$('#container').append(div);
div.css('width');//add this line
div.css('width', '200px');

Working here:

var div = $('<div class="trans" />');
$('#container').append(div);

var div = $('<div />').addClass('trans');
    $('#container').append(div);
    div.css('width');//add this line
    div.css('width', '200px');
.trans {
    width: 20px;
    height: 20px;
    background-color: cyan;
    -webkit-transition: all 5s ease;
       -moz-transition: all 5s ease;
        -ie-transition: all 5s ease;
         -o-transition: all 5s ease;
            transition: all 5s ease;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="container"></div>

As explained in the comments below by Lucero, doing it this way is necessary to force the browser to calculate an actual value rather than "auto", "inherit" or similar. Without an actual value, the browser will not know the new value to be a change from the previous.

EzioMercer
  • 1,502
  • 2
  • 7
  • 23
Torin Finnemann
  • 771
  • 9
  • 11
  • 16
    +1 - Depending on the existing values (such as "none"), the browser needs to first process the new layout to recognize the change that triggers the transition (e.g. numeric to numeric). Reading a CSS property forces the layout to be computed. – Lucero Jan 07 '14 at 15:39
  • 2
    Saved me some time of research, exactly what I needed. Such a fool requirement... – BernaMariano Mar 17 '14 at 21:07
  • 1
    This is a better and cleaner solution than using setTimeout. – vinayakj Nov 23 '15 at 19:20
21

here are two ways to do this.

1 - CSS transitions

by using setTimeout the addClass method will run after instead of along with the preceding script so that the transition event will fire

example jsfiddle

jQuery:

var div = $('<div class="trans" />');
$('#container').append(div);
// set the class change to run 1ms after adding the div
setTimeout(function() {div.addClass('wide')}, 1); 

CSS:

.trans {
    width: 20px;
    height: 20px;
    background-color: cyan;
    -webkit-transition: all 5s ease;
       -moz-transition: all 5s ease;
        -ie-transition: all 5s ease;
         -o-transition: all 5s ease;
            transition: all 5s ease;
}
.wide {
    width: 200px;
}

2 - jQuery's .animate() function

example jsfiddle

jQuery:

var div = $('<div class="trans" />');
$('#container').append(div);
div.animate({'width': '200px'}, 5000); // 5 sec animation

CSS:

.trans {
    width: 20px;
    height: 20px;
    background-color: cyan;
}
MikeM
  • 27,227
  • 4
  • 64
  • 80
6

Try calling jQuery's .offset() method.

When the browser gets the offset, it triggers a reflow/repaint/layout event which allows the transitions to work.

See this fiddle: http://jsfiddle.net/FYPpt/199/

Also see -- http://gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-webkit.html

theozero
  • 592
  • 5
  • 8
  • 1
    this is just the same as when using `div.width();` like in the accepted answer – MMachinegun Dec 10 '14 at 18:39
  • 1
    This is cleaner because it isn't dependent on the CSS transition value, which I assume is the reason width is suggested in the accepted answer. – roydukkey Nov 22 '15 at 04:19
  • Thank you, this is especially useful if you change a large number of properties or don't want to hard-code the CSS properties in your JS code. – SapuSeven Aug 02 '17 at 09:20
4

In response to the bounty:

If you don't have the luxury of knowing which property is being transitioned or simply don't think it's good practice to force a setTimeout to get an animation to fire, you can force a reflow in another way.

The reason that setTimeout works in the first place is due to the fact that you're causing a full document reflow in the meantime, even if the duration is set to 0. That's not the only way to cause a reflow.


Link to a fiddle

JavaScript

for (x = Math.ceil(Math.random() * 10), i=0;i<x;i++){
    $('<div />').appendTo($('#container')).addClass('trans');
}

var divs = $('#container div');
var x = divs.eq(Math.ceil(Math.random() * divs.length));
x[0].offsetHeight;
x.addClass('wide');

With this JavaScript we're randomly adding between 1 and 10 div elements, and then selecting one of those at random and adding the wide class.

You'll notice an odd line of JavaScript in there, namely x[0].offsetHeight. This is the lynchpin to the whole operation. Calling offsetHeight triggers the document to reflow without using a setTimeout or querying the CSS value.

What a reflow is in relation to the DOM:

A reflow computes the layout of the page. A reflow on an element recomputes the dimensions and position of the element, and it also triggers further reflows on that element’s children, ancestors and elements that appear after it in the DOM. Then it calls a final repaint. Reflowing is very expensive, but unfortunately it can be triggered easily.

So please be judicious in how you use these functions. Not too much of a concern with most modern browsers (as jQuery is notorious for firing several reflows), but still something to consider before adding something like this solution all over your website.


Important note: This is no more or less efficient than a setTimeout call. This isn't a magic bullet solution, and it's doing the same thing under the hood.


Here's the corresponding CSS, for reference:

.trans {
    width: 20px;
    height: 20px;
    background-color: cyan;
    -webkit-transition: all 5s ease;
       -moz-transition: all 5s ease;
         -o-transition: all 5s ease;
            transition: all 5s ease;
}

.trans.wide {
    width: 200px;
}
Josh Burgess
  • 9,327
  • 33
  • 46
  • 1
    Actually calling `width()` seems to do the job, even if other props are transitioned. I feel silly now for not testing it :) – nice ass Dec 12 '14 at 19:38
2

as @onetrickpony was asking, what if all rules are in the CSS file and should not be added in the JS-function.

Based on the answer from Torin Finnemann, just with a slight change:

Add an extra class where rules are defined: new-rules

var div = $('<div class="trans" />');
$('#container').append(div);

var div = $('<div />').addClass('trans');
$('#container').append(div);
div.height();
div.addClass('new-rules');

in your CSS have the class predefined:

.trans.new-rules {
    width: 200px;
    background: yellow;
}

now there is no need tingling with the JS when changing the CSS. Change it in the CSS-file at new-rules :)

http://jsfiddle.net/FYPpt/201/

MMachinegun
  • 3,044
  • 2
  • 27
  • 44
1
var div = $('<div />');

$('#container').append(div);

div.css('width', '20px').addClass('trans', function() {
   div.css('width', '200px') 
});

Creates a new DIV element and adds the 'trans' class to that element in order to fire the CSS transition

mike510a
  • 2,102
  • 1
  • 11
  • 27
  • What is this..? What does this do..? – T J Dec 12 '14 at 19:28
  • Re-ordering the functions so that the div is created first, followed by addClass with a callback to set the width after the addclasw transition -- it will definately fire that way ... not sure the exact reqs of the code but i know this will work – mike510a Dec 15 '14 at 03:13
  • Please [edit] your answer and add the explanation there... code only answers are not encouraged here... – T J Dec 15 '14 at 06:52