DGibbs DGibbs - 26 days ago 16
CSS Question

Swapping <div> position causes loss of CSS3 transition effect

I've got an issue which seems to be caused by altering the position of two

<divs>
within their parent, essentially re-ordering them.

Both divs have a
transition
css rule applied like so:

-webkit-transition: all .2s ease-in-out;
-moz-transition: all .2s ease-in-out;
transition: all .2s ease-in-out;


When I call this to swap the position:

function swapElements(first, second) {
$(first).insertBefore(second);
}


The
first
element is not having the transition effect applied by the browser, it essentially doesn't animate at all after having been swapped with the previous/second element.

This is a problem as I'm listening to the transition end callback to then do some more work within the
<div>
(initializing a gallery):

if (Modernizr.csstransitions) {
$(roomColumn).one('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function () {
if (!gallery.hasClass('slick-initialized')) {
$(gallery).slick({
lazyLoad: 'progressive',
slidesToShow: 1,
slidesToScroll: 1,
autoplay: false,
dots: false,
adaptiveHeight: false
});
}
});
}


Because the browser isn't doing the transition for this element, the event
.one('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function (){});
is never fired for this
<div>
and so the gallery is not initialized for it.

Does anyone have any ideas on what could be causing this behaviour?

Edit: Here is a very crude JS fiddle. You will need to expand the output window so that are four columns.

As you can see, clicking any of the first three gives you an animation effect, clicking the last
<div>
in the row, labelled
LAST ROOM
, does not cause any animation to occur.

Interestingly, if I wrap the
<div>
swapping code in a
setTimeout
call it works fine, it seems like a timing issue, obviously I don't want to rely on this as a hacky fix though, it also causes the div to jump into place which is undesirable.

Answer

The problem happens because after swapping, you are also removing the original classes (for the small box display) and replacing with the new ones (for the larger display). All three happen within a single blocking call. Whenever an element is added, the browser would need to trigger a reflow but it doesn't trigger the reflow after each step as doing so will affect performance. So, the reflow happens at the end (after execution of the removeClass and addClass also) and thus there is only an element with new classes which is being painted on the screen - no state change and so no transition also.

To overcome this, we need to force the UA to trigger a reflow/repaint after swapping but before the class changes. This can be done in two ways - one is to force the class change statements into a different function call (anonymous function using setTimeout) and the other is by forcing the UA to calculate things like clientHeight etc (they force a repaint because they can only then calculate the actual height).

Demo with setTimeout | Demo with clientHeight

Comments