1

What I would like to do (broke):

<div></div>
<button>go</button>
$('button').click(function () {
    $('div').css({
        'transition': 'left 1000ms'
    }).addClass('left').addClass('left_more');
});

http://jsfiddle.net/0bm4wq7h/13/

Still broke:

<div></div>
<button>go</button>
$('button').click(function () {
    $('div').css({
        'transition': 'left 1000ms'
    }).addClass('left');
    console.log('test');
    $('div').addClass('left_more');
});

http://jsfiddle.net/fwL3dwz2/3/

But this works:

<div></div>
<button>go</button>
$('button').click(function () {
    $('div').css({
        'transition': 'left 1000ms'
    }).addClass('left');
    console.log($('div').css('left'));
    $('div').addClass('left_more');
});

http://jsfiddle.net/j8x0dzbz/5/

I know I need a starting point for my CSS transition. That is why I added the left class.

Why does jQuery not do the transition until my #3?

Update:

So I had accepted Stryner's answer, because it was working for me and now I'm having the same issue again. The above code was a simplified version of this JavaScript:

$('#screen_wrapper img:eq(0)').removeClass().addClass('prep'); //starting point
window.getComputedStyle(document.getElementById('photo'+0)).left; //force the the styling to get recomputated using raw JavaScript 
$('#screen_wrapper img:eq(0)').css('left');//force the the styling to get recomputated using jQuery
$('#screen_wrapper img:eq(0)').addClass('animate_in');//animate to the "animate_in" class coordinates from the "prep" class coordinates

What's happening is that I'm getting the animation starting from coordinates prior to the prep class.

Here's the prep class:

#screen_wrapper img.prep {
    top: 0px;
    left: 506px;
}

But the image is actually starting from this class which is removed using the removeClass() jQuery method:

.animate_out {
    visibility: visible;
    top: 0px;
    left: -506px;
}

The transition property is working properly:

$('#screen_wrapper img').css('transition','left 1000ms');

I have doubts that these force recalculation of styling:

window.getComputedStyle(document.getElementById('photo'+0)).left;
$('#screen_wrapper img:eq(0)').css('left');

I'm using Chromium:

Version 45.0.2454.101 Ubuntu 14.04 (64-bit)

Update

Example of it not working: http://jsfiddle.net/me8ukkLe/12/

7
  • Questions seeking debugging help ("why isn't this code working?") must include the desired behavior, a specific problem or error and the shortest code necessary to reproduce it in the question itself. Commented Nov 24, 2015 at 18:55
  • I started off with what I would like to do? Commented Nov 24, 2015 at 18:57
  • I definitely saw the desired behavior, and some short code to reproduce the issue. Not sure what the point of placing that comment on this question was. Looks fine to me. Commented Nov 24, 2015 at 19:55
  • @TravisJ The original question only contained the description and the links to jsfiddle. I've inserted the code snippets from the fiddles into the question itself. Maybe the comment should have been added to the edit instead of the question... Commented Nov 24, 2015 at 20:18
  • 1
    @Dusty I think the layout recalculation is still occurring. If I were to guess, the reason that the image starts from the removed class is due to the transition. When you force the recalculation, it starts transitioning to the value of the new class, but since you're instantly adding a new class after, that transition is getting interrupted. The reason this didn't occur in your original example is that the <div> had no initial left value to transition from. If you add it, it does the same thing. Also see jsfiddle.net/me8ukkLe/13 Commented Dec 1, 2015 at 20:14

4 Answers 4

4

Your transition works in case three when it calls $('div').css('left') because jQuery will call the method window.getComputedStyle (or a very similar method depending on browser compatibility issues). In a blog post by Tim Taubert (a Mozilla Employee), the trick is described:

getComputedStyle() in combination with accessing a property value actually flushes all pending style changes and forces the layout engine to compute our <div>’s current state.

Without forcing this layout recalculation, the recalculation is delayed until after both classes (left and left-more) are added, which will calculate its position at 400px.

Example Fiddle - using getComputedStyle and accessing .left

Sign up to request clarification or add additional context in comments.

Comments

1

"CSS3 transitions allows you to change property values smoothly (from one value to another), over a given duration."

1st Scenario posted :

In order to have the css transition to work, you need to specify the css property to the element on which you want to do the transition. In your example, you are doing a transition on the left property but it's initial value is not defined in the div css.

In order to fix it, just add left property.

div {
    position: absolute;
    width: 10px;
    height: 10px;
    background-color: red;
    left: 0px;
}

Working example : http://jsfiddle.net/0bm4wq7h/14/

2nd scenario posted vs 3rd scenario posted:

Even though both examples doesn't have left property defined for the div, the reason why the 3rd scenario works as compared to the 2nd scenario is because of the delay caused by console.log.

In the first statement,

$('div').css({
        'transition': 'left 1000ms'
    }).addClass('left');

class left is added to the div element, which internally adds the left property. But adding the console.log($('div').css('left') invokes window.getComputedStyle ( as mentioned by Stryner ) which registers the computed value and adding $('div').addClass('left_more'); basically gives it an opportunity to perform the transition from left : 100px to left : 600px.

9 Comments

I believe @Dusty is looking for why the first two fiddles aren't working, as opposed to how to solve the matter.
added the explaination for that.
Example #2 uses a console log message as well
There is no "delay". The comment on the need for the property to exist is accurate though.
@TravisJ: Its kind of weird of an alert() statement substitute to console.log('test') solves the problem too. So i suspect it to be some kind of delay response
|
0

Good question! This behaviour definitely seems weird at first. It's also a little tricky to explain this clearly, but to start, understand the following:

1) Javascript functions execute atomically

In JS, functions always run from beginning to end without any possibility of some other operation occurring midway through. This is the same as saying that JS is a single-threaded language.

2) The browser can't interrupt javascript either

Not only is JS code prevented from running midway through a function, but the browser tab in which the code is running will also not interject! This means that (nearly) EVERYTHING on a webpage is halted (repaints, animations, stylesheet application, etc) when a JS function is running. If you want to test this, you can try running while (true) { var i = 'yo'; } in the console. (Warning: this will make you sad)

3) State inside of JS functions is invisible to browsers

Because the browser can't interrupt in the middle of a JS function it means the browser can never know any state that occurs mid-way through said function. The browser can only act based off of the state that remains once a function finishes. Not clear? To illustrate:

var someGlobalValue = null;

function lol() {
    someGlobalValue = 'hi';
    someGlobalValue = 'hello';
    someGlobalValue = 'ok';
}

When the function lol is run, someGlobalValue assumes multiple values, but the browser will only ever be aware of the last one, because it would have to interject midway through in order to see the others (which it can't do!)

4) CSS State inside of JS functions is similarly invisible to browsers

The same applies to css state! (And now you may see that I am, in fact, beginning to answer your question).

If you call the following function:

function lol() {
    $('.thing').css('left', '10px');
    $('.thing').css('left', '30px');
}

The browser will never apply the left: 10px value because it takes no actions midway through a function! Only the results of a function, one it is complete, can be worked with by the browser.

Answering your questions!!

fiddle 1

The left_more class is added immediately after the left class - the browser never sees the left class, and applies css styling after the function ends - applying the left_more class, but since no initial left value was present there is no animation. (The css cascades; while both classes are present, left_more fully overwrites left)

fiddle 2

Same issue - the left class is overwritten before the browser can process it

fiddle 3

This works because the value is set with a call to css, and then is NOT overwritten because addClass is used to set where the value should animate to. The console.log is irrelevant - all that is important is the css function call. Once that function completes, there are 2 pieces of information, 1 being the css value, the other being the class value. This contrasts the other example where there is only one piece of information left after the function runs: the class value.

If you wanted to work with only classes, and still get the transition going, the flow would have to look like this:

1) Add left class 2) Exit function, which allows the browser to view the state 3) Add left_more class

Sry for the essay lol. But I think this needed a long explanation because of the subtlety of the issue.

Comments

0

There are several issues going on here. In the examples given, the reason that the transition does not occur when the first left class is added is because in order for the rendering engine to animate a property, that property needs to have a value already. In this case, there is no value for left and as a result the transition does not occur.

The reason why it does not work when chained is because of specificity. jQuery will add both classes and when the call to render the page is made the last added definition for left is applied due to their shared specificity.

The reason why it appears to work from adding the class at a separate time versus chained is because the rendering engine was implicitly called from jQuery when the console.log accessed the css values of the element. This was pointed out by Stryner in his answer here: https://stackoverflow.com/a/33902053/1026459 . It was a very nice find.

Here is an example of that being applied so that the value of left doesn't need to be guessed at.

The meat of what happens is the offset of the element is found to get the pixel offset for left. Then that value is applied to the left style property. At that point while the element still hasn't changed position the rendering engine is called to update the left property. The property is then removed to ensure that the specificity does not take precedence over the class definition, and then the class definition is applied. This will facilitate the transition on first encounter.

jsFiddle Demo

$('button').click(function() {
    $('div').css({'transition':'left 1000ms'}).each(function(){	
        $(this).css('left',$(this).offset().left); 
        window.getComputedStyle(this,null).getPropertyValue("left");
    }).css('left','').addClass('left');
});
button {
    margin-top: 30px;
}

div {
    position: absolute;
    width: 10px;
    height: 10px;
    background-color: red;
}

.left {
    left: 100px;
}

.left_more {
    left: 400px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div></div>
<button>go</button>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.