49

I've got a script executing on $(document).ready() that's supposed to vertically align block element in my layout. 90% of the time, it works without issue. However, for that extra 10% one of two things happens:

  • There's an obvious lag in the time it takes to do the centering, and the block elements jump into position. This could simply be performance related - as the page size is often large and there is a fair amount of javascript that is executing at once.

  • The centering will completely mess up, and the block element will either pushed down too far or not far enough. It appears as if it tried to calculate the height, but was getting improper measurements.

Is there any reason why executing a script on DOM-ready would not have all the correct CSS values injected into the DOM yet? (all CSS is in the <head> via a <link>).

Also, here's the script that's causing the issue (yes, it's been taken straight from here):

 (function ($) {
    // VERTICALLY ALIGN FUNCTION
    $.fn.vAlign = function() {
      return this.each(function(i) {
        var ah = $(this).height();
        var ph = $(this).parent().height();
        var mh = (ph - ah) / 2;
        $(this).css('margin-top', mh);
      });
    };
  })(jQuery);

Thanks.

geowa4
  • 36,556
  • 14
  • 85
  • 105
Bryan M.
  • 16,594
  • 8
  • 44
  • 58
  • Are there images involved? Even after styles are computed, image loading can cause relayout. – sam Mar 06 '15 at 04:44

5 Answers5

64

From the 1.3 release notes:

The ready() method no longer tries to make any guarantees about waiting for all stylesheets to be loaded. Instead all CSS files should be included before the scripts on the page. More Information

From the ready(fn) documentation:

Note: Please make sure that all stylesheets are included before your scripts (especially those that call the ready function). Doing so will make sure that all element properties are correctly defined before jQuery code begins executing. Failure to do this will cause sporadic problems, especially on WebKit-based browsers such as Safari.

Note that the above is not even about actually rendering the CSS, so you may still see the screen change when ready() kicks in. But it should save you from problems.

Actually, I find it a bit strange that just putting the CSS above the JS will solve all issues. The CSS is loaded asynchronously, so JS loading can start and finish while the CSS is still being downloaded. So if the above is a solution, then executing any JS code is then halted until all earlier requests have completed?

I did some testing, and indeed, sometimes JS is delayed until the CSS is loaded. I don't know why, because the waterfall shows that the JS has completed loading long before downloading the CSS has finished.

See JS Bin for some HTML and its results (this has a 10 second delay), and see webpagetest.org for its waterfall results. This uses some script from Steve Souders' cuzillion.com to mimic slow responses. In the waterfall, the reference to resource.cgi is the CSS. So, in Internet Explorer, the first external JS starts to load right after the CSS was requested (but that CSS will take another 10 seconds to finish). But the second <script> tag is not executed until the CSS has finished loading as well:

<link rel="stylesheet" type="text/css" href=".../a script that delays.cgi" />

<script type="text/javascript" src=".../jquery.min.js"></script> 

<script type="text/javascript"> 
  alert("start after the CSS has fully loaded"); 
  $(document).ready(function() { 
    $("p").addClass("sleepcgi"); 
    alert("ready"); 
  });         
</script> 

Waterfall with a single external JS script

Another test with a second external JS after getting jQuery, shows that the download of the second JS is not started until the CSS has loaded. Here, the first reference to resource.cgi is the CSS, the second the JS:

Waterfall with two external JS scripts

Moving the stylesheet below all JS indeed shows that the JS (including the ready function) runs much earlier, but even then the jQuery-applied class --which is yet unknown when the JS runs-- is used correctly in my quick tests in Safari and Firefox. But it makes sense that things like $(this).height() will yield wrong values at that time.

However, additional testing shows that it is not a generic rule that JS is halted until earlier defined CSS is loaded. There seems to be some combination with using external JS and CSS. I don't know how this works.

Last notes: as JS Bin includes Google Analytics in each script when running from the bare URL (like jsbin.com/aqeno, the test results are actually changed by JS Bin... It seems that the Output tab on the edit URL such as jsbin.com/aqeno/edit does not include the additional Google Analytics things, and surely yields different results, but that URL is hard to test using webpagetest.org. The reference to Stylesheets Block Downloads in Firefox and JavaScript Execution in IE as given by strager is a good start for a better understanding, but I got many questions left... Also note Steve Souders' IE8 Parallel Script Loading to make things even more complicated. (The waterfalls above are created using IE7.)

Maybe one should simply believe the release notes and documentation...

Pang
  • 8,605
  • 144
  • 77
  • 113
Arjan
  • 20,227
  • 10
  • 57
  • 70
  • Cuzillion (http://stevesouders.com/cuzillion/) should help you understand how different types of objects are loaded in different browsers. – strager Aug 24 '09 at 23:54
  • ...but it's still **not the definitive answer**. Maybe it even matters if things are in `` or ``? And given the *Stylesheets Block Downloads in Firefox and JavaScript Execution in IE* (so, what about other browsers?) and *IE8 Parallel Script Loading* I guess many problems can arise for this vertical aligning script. And by the way: the waterfall for the latter in IE8 at http://www.webpagetest.org/result/090825_24AP/ does NOT show the parallel loading, and looks like IE7 at http://www.webpagetest.org/result/090825_24AQ/ – Arjan Aug 25 '09 at 07:43
  • @Bryan, I think your question, and especially that *IE8 Parallel Script Loading*, will make me sleep worse the coming weeks... ;-) As for your question: see the following JS Bin for a version with your vAlign function (slightly altered to allow for using the provided CSS), which indeed shows that `$(this).height();` may yield zero when the CSS is loaded *after* the JavaScript, but seems fine when loaded *before* that. But still, not knowing the *why*, I'm afraid each browser may behave differently: http://jsbin.com/ofola/edit – Arjan Aug 25 '09 at 15:03
  • 2
    Browsers download stylesheets asynchronously but block scripts *below them* from executing until the stylesheets have loaded. They guessed that authors would want to query style information in subsequent scripts, so they have to block execution to guarantee that the styles are available. – sam Oct 06 '13 at 20:48
  • 1
    @sam It actually blocks until the element is rendered, so js can query the values, not just until the stylesheet loads. This is called layout trashing and it's a common source of performance issues. – Juan Guerrero Oct 06 '13 at 22:06
15

CSS/JavaScript/JQuery ordering doesn't work for me, but the following does:

$(window).load(function() { $('#abc')...} );
Martin Dvorak
  • 742
  • 8
  • 15
  • same here. I had trouble in IE9 with js running before styles were rendered but replacing $(document).ready with this fixed my issues. bizarro. Thanks anyway :) – Code Novitiate Sep 11 '12 at 21:21
  • even jquery writes this: "While JavaScript provides the load event for executing code when a page is rendered, this event does not get triggered until all assets such as images have been completely received." http://api.jquery.com/ready/ so I also will try to solve using this – Dariux Jul 04 '14 at 08:17
  • I tested, css/js reordering doesn't fix it for me while loading a external stylesheet this did in both Opera and Chrome. – csga5000 Dec 07 '14 at 02:00
  • 2
    This is the nuclear option. The load event often happens _way_ later than dom-ready - just consider the effect of heavy image assets, Facebook iframes etc. If a script is delayed until after the load event, it may take many seconds until it executes. – hashchange Mar 05 '15 at 21:45
6

The DOM ready fires when all the DOM nodes are available. It has nothing to do with CSS. Try positioning the style before or try loading it differently.

fphilipe
  • 8,542
  • 1
  • 29
  • 47
4

To the best of my knowledge the ready event is fired when the DOM is loaded - which means that all the blocking requests (i.e. JS) have loaded and the DOM tree is completely graphed. The ready state in IE relies on a slower event trigger (document.readyState change vs DOMContentLoaded) than most other browsers so the timing is browser dependant also.

The existence of non-blocking requests (such as CSS and images) is completely asynchronous and unrelated to the ready state. If you are in a position where you require such resources you need to depend on the good old onload event.

annakata
  • 70,224
  • 16
  • 111
  • 179
  • While doing some testing today I learned that somehow downloading CSS can be blocking (for executing some next ` – Arjan Aug 24 '09 at 23:48
  • I wasn't the one who downvoted you, but you may want to look at this: http://stevesouders.com/cuzillion/?ex=2&title=Stylesheets+Block+Downloads – strager Aug 24 '09 at 23:57
  • CSS downloads are non-blocking but [they can block execution of subsequent scripts](http://www.whatwg.org/specs/web-apps/current-work/multipage/semantics.html#has-no-style-sheet-that-is-blocking-scripts). – sam Oct 06 '13 at 21:28
  • The above link has got to the be the _MOST_ confusing way to say iframe scripts can get blocked by stylesheets in the parent. Am I misunderstanding? Seriously! – Ryan Taylor May 30 '14 at 18:32
4

According to HTML5, DOMContentLoaded is a plain DOM ready event without taking stylesheets into account. However, the HTML5 parsing algorithm require browsers to defer the execution of scripts until all previous stylesheets are loaded. (DOMContentLoaded and stylesheets)

In molily's tests (2010),

  • IE and Firefox blocked all subsequent script execution until stylesheets loaded
  • Webkit blocked subsequent execution only for external scripts (<script src>)
  • Opera did not block subsequent execution for any scripts

All modern browsers now support DOMContentLoaded (2017) so they may have standardized this behavior by now.

sam
  • 35,828
  • 2
  • 38
  • 36