Posts Tagged ‘offsetWidth’

Triggering reflow for CSS3 transitions

There’s a lot written on minimizing reflow of the DOM, but one interesting situation is actually trying to force reflow in order to apply a CSS transition rule when the element is pushed into the DOM render tree (e.g. display:none to display:block, display:inline, etc.).

Problem

.test-block { width:50px; height:50px; background:#000; margin:10px; opacity:1; transition:opacity 0.5s; }
.test-block.invisible { opacity:0; }
.test-block.hide { display:none; }
<div class="ia-fadein-block test-block invisible hide"></div> $('.ia-fadein-block').removeClass('hide');
$(
'.ia-fadein-block').removeClass('invisible');

If we remove the hide and invisible classes from the div (as shown above), you’d assume that the div would transition smoothly to opacity:1 in 0.5s; however, this is not the case, the div will go to opacity:1 instantly.

What’s happening is that the removal of both classes occur prior to the browser adding the div to the DOM render tree; when the div gets rendered, it’s as if it never had the hide nor invisible class.

Reflow needs to be triggered before the invisible class is removed in order for the transition to work as expected.

Deprecated solution: setTimeout(func,0)

A setTimeout() with a delay of 0 was my go-to solution for the longest while. Here, the function argument specified would handle whatever needed to occur after the element was added to the DOM render tree (for the example presented above, this would mean the invisible class being removed in the function specified). The thinking behind using setTimeout() was that reflow would occur at some point in the current execution context and before the execution of the setTimeout() callback. This seemed true for a while, but with one of the Firefox releases last year (I don’t know specifically which) I noticed things breaking, with the Javascript engine executing the callback before reflow occured.

I did some quick hacks to fix existing code by increasing the time span before the timeout was triggered (typically 50ms-100ms), but this made a nasty hack even nastier and I wanted a more reliable solution.

Current solution: Trigger reflow manually

There are a number of calls that will trigger reflow on the DOM, but the most reliable calls, that always force reflow, seem to be the ones that return any sort of measurement that must be calculated (e.g. offsetWidth) on an element. With this in mind, the Javascript code can be modified as follows:

$('.ia-fadein-block').removeClass('hide');

// force reflow
$('.ia-fadein-block')[0].offsetWidth;
//

$('.ia-fadein-block').removeClass('invisible');

The code is for demonstration purposes; for repeated use, the hack to force reflow should really be encapsulated in a function, as there is no guarantee that the reflow behavior triggered by querying offsetWidth will remain consistent.

Ideal solution: API Method to trigger reflow or delay certain style changes post-reflow

The solution presented above is still a hack and may very well break in the future, as there is no guarantee that browsers won’t cache properties like offsetWidth in further attempts to minimize reflow. A robust, future-proof, solution is needed and it’s amazing one hasn’t come to fruition as yet.

A Mozilla bug report documents this issue and David Baron presents some workable solutions.