We have some JavaScript code that resizes <div/> elements (adjusts height/width/padding/margin etc.) based on the dimensions of a parent <div/> element, specifically the height and width of the parent we use in the calculation. The parent div height and width is defined in a external CSS file, and <link> is used to include the stylesheet, in the <head> section of the page. All stylesheets are included before javascripts. The JavaScript resize code is fired inside of a jQuery document ready event using the standard $(function() {}). All stylesheets and javascripts automatically have timestamps appended to them (Rails application) to ensure they are not cached (in development).
In Firefox 3.6 the resizing works every time. However in Safari 5, occasionally the resizing fails, because the parent height/width is incorrect, which appears to be because the stylesheet has not loaded before the JavaScript. Frustratingly, this happens every few times, and is different depending on the browser/machine load. I can put the resizing code inside a setTimeout() and delay it by 1 second or so and get it to work reliably, but this is not a releasable solution because it is too slow, and still varies by machine. Ideally there would be a way to guarantee the stylesheet is downloaded, then fire the JS resize code.
First of all, is this a bad design, to have JS resizing based on CSS loaded from an external stylesheet? I'm considering trying to place the height and width inline so it is rendered from the server, but this requires significant app code change and tighter coupling to the UI, and I'd like to avoid inline style if possible. Is there a webkit event by chance when stylesheets are loaded? I don't think there is. Any suggestions on a method other than a timeout to ensure all stylesheets are loaded before firing JavaScript? Anyone else experienced differences in stylesheet load order/timing between Webkit and other browsers and have a solution to share? I could use some fresh thinking on this problem.
Note: in general for images, we use the load ($(thing).load(fn(){})) event on images to ensure they are loaded before applying any JavaScript to them. That would be a good pattern to follow if such a thing existed. Thanks!
Co-worker (#johnreilly) found a couple solutions, one polls the document.readyState property when the browser is Safari looking for a value of complete.
Get the real width and height of an image with JavaScript? (in Safari/Chrome)
Another creates a cssLoaded custom event and uses :after pseudo-selector to avoid polling. http://stilbuero.de/demo/cssloaded/
So the first solution is acceptable. I'm putting it in the main javascript file and using $.trigger() to emit a custom event to our handlers that depend on Webkit having fully downloaded the stylesheets.
Related
I use jQuery (which seems to use a glorified form of innerHTML document writing), to add a piece of HTML to the DOM:
$newElem=$(HTML).appendTo($container);
The said HTML piece contains CSS links, which seem to load async. Images also load async.
I need some form of load event similar to window.load when async content is done fetching AND done parsing (i.e. CSS), because based on that I trigger a container resize/rearrange function, and sizing obviously depends on CSS, async images (and even async fonts but this last point is not an immediate concern for me). So how to get a proper load event for the DOM-added HTML?
I don't think there's a built-in "load" event that's fired when all the resources requested by the dynamically added elements are finished loading.
You can probably implement this though if you're sufficiently motivated.
There's waitForImages jQuery plugin, that goes through the given DOM subtree, looking for images (<img> tags as well as references to images in computed CSS styles). It creates an <img> element for each image referenced from CSS to track its load status (as discussed here).
It doesn't support:
content:url() images (should be easy to add)
Tracking resources referenced from dynamically loaded CSS. You can use a similar approach to find all the <link> elements in the given subtree, and use their load event (supported in all major browsers now) to wait until the CSS is loaded. After CSS finishes loading, run waitForImages to track the image loads.
On our site we load stylesheets dynamically based on whether the display is retina or not. Right now, we are using document.write for each <link href="stylesheet.css"> we insert in the page, with different css files if the display is retina.
However, this hurts performance because it causes the css files to load synchronously, as the browser has no way of parsing the javascript to load the next file before the previous one is finished. I believe we can reduce page load time if we take advantage of modern browsers' capability to look ahead and fetch resources asynchronously - in another words, if we load the CSS files in parallel instead.
My current solution is to create a <link id="link-tag-id" href=""> tag for every stylesheet to be loaded, immediately followed by a script which determines the retina status, then fills in the quotations with the appropriate file, along the lines of:
document.getElementById("link-tag-id").setAttribute("href", "retina-stylesheet.css")
This seems to work fine, and when I examine the network waterfalls in Chrome developer tools, as well as on WebPageTest.org (running Chrome, Firefox, and IE), the stylesheets indeed load in parallel. However, it seems a little hacky. I was wondering if there are any dangers to creating a <link> tag with an empty href attribute, and if so, what are they?
On a broader note, are there any other recommendations on how to load CSS dynamically and asynchronously?
Thanks for your help!
EDIT: I just discovered this works too:
document.getElementById("link-tag-id").href = "retina-stylesheet.css"
You could use media queries inside your stylesheet to determine if the display is a retina display, then load in the required CSS.
http://css-tricks.com/snippets/css/retina-display-media-query/
http://mobile.smashingmagazine.com/2010/07/19/how-to-use-css3-media-queries-to-create-a-mobile-version-of-your-website/
On a website, I'm experiencing a "flash" of white that occurs between page loads. It looks bad because I'm using a background image and when the page loads, the background images flash before it comes onto the screen (take a look for yourself). This issues occurs in chrome and IE but not in firefox.
The site has a way of preloading stuff. Every element on the page is in a div wrapper #website which is initially at display:none, and every image is in a div wrapper #website-images which is also hidden. Then the site (using a jquery plugin) checks to see if all the images in #website-images are done loading, once they are a cookie is set to remember that this user has loaded the images already so it won't go through the preloading process once they go to another page or reload the current one, then a call to $("#website").show() is made to display the webpage.
So what could be causing this flickering between the page loads? Is it my way of preloading images? I've added different doctypes, and changed meta information but NOTHING has worked. I'm really lost here, does anyone have any ideas or insights?
This is happening because the DOMLoaded event is fired enough milliseconds before the page actually renders.
In a nutshell, this means you have to optimise your website's speed. This doesn't mean to make it download faster, but it means to download in the correct order, in a non-blocking way.
Step one: Your markup
1)
It seems there is a lot you can do to optimise your markup. Firstly, the order of stylesheets and JavaScripts can be optimised. To ensure CSS files are downloaded asynchronously, you always have to include external CSS before external JavaScript files. style.css is downloaded after some/all of your JavaScript calls.
There is 1 script block found in the head between an external CSS file and another resource. To allow parallel downloading, move the inline script before the external CSS file, or after the next resource.
2)
Your main JavaScript file is inline within your markup. Not only does this block the page download until the script has finished downloading, but having it before your content is probably causing (or adding to) the white flash.
Loading your script asynchronously in the head is my preferred method. You will then have to trigger your script when the DOM has finished loading, or you can achieve the same result by placing the script at the bottom of the body tag.
Step two: Harness the browser's capabilities
1) Looking at the http headers, there are 28 items being served as separate HTTP calls, that are not being cached on the browser (including the html pages, jpg images, stylesheets and JavaScript files).
These items are explicitly non-cacheable, and this can be easily fixed by editing your webserver's configuration.
2) Enable gzip compression. Most web browsers (yes, even IE) supports gzip decompression, and most (if not all) web servers support compressing using gzip. You could even go overkill and look into SPDY, which is an alternative lighter HTTP protocol (supported in Chrome and Firefox).
Step three: Content serving
There are around 30 individual items being served from your domain. Firstly, consider how you could reduce this number of requests. 30 HTTP requests per page view is a lot. You can combat this using the following methods:
1) Paralleled downloads across multiple hostnames. Browsers currently limit the number of concurrent connections to a single domain. Serving your images from a separate domain (for example, img.bigtim.ca) can allow them to be served in parallel to other content.
2) Combine multiple items into one. Many items that are downloaded are purely style content, such as the logo, menu elements, etc. These can be combined into a single image (downloaded only once), and split using CSS. This is called CSS spriting. Stack Overflow does this: look here.
3) If you cannot reduce the amount of items needing downloading, you could reduce the load on your server (and in turn, the client's browser) by serving static content from a cookieless domain. Stack Overflow does this with all their static content such as images, stylesheets and scripts.
Step four: Optimise your own code
There's only so much that HTTP and browser technology can do to help your website's speed. This last step is down to you.
1) Is there any reason you choose to host jquery yourself? Jquery's download page shows multiple CDNs where you can point to for speedy, cached script downloading.
2) There are currently over 20 unused CSS rules within your stylesheets (that's 36% of your entire CSS file). Have a re-think of what is really needed.
3) The main chunk of JavaScript (at the top of your body tag) seems to be a hack to attempt to speed things up, but is probably not helping anything.
A cookie is being set to specify whether or not the page has faded in yet. Not only are you using JavaScript to perform a transition which can happily be performed by CSS, but more than half of the script is used to define the functionality for reading and writing the cookie.
Seeing things like this: $("body").css ("background-image", "url('images/background.png')"); and $("#website").show (); usually gets me ranting about "separation of concerns", but this answer is long enough now so hopefully you can see that it is bad practice to mix style and functionality in the same code.
Addendum: Looking at the code, there is no need for jquery at all to
perform what you are doing. But then again, there is no need to
perform what you are doing, so you could probably do better without any
JavaScript at all.
Move your javascript to the end of the html just before closing the body tags. Sometimes it helps.
I know this is old thread but here is a hack I tried and works.
The idea is not to display anything while CSS is loaded completely.
in html file:
<body style="display:none">
in your CSS, the last line:
body{display:block !important}
CSS is render-blocking.
Divide you CSS into 2 parts -
Critical CSS
Non-Critical CSS
Make Critical CSS load with the page. It should come embedded within the head tag.
Make Non-critical CSS lazy load via ajax.
This will result in serious performance optimization in your webpage leading to less white-screen time.
Also, you can consider loading your Javascript in async/defer way.
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.
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>
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:
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...
CSS/JavaScript/JQuery ordering doesn't work for me, but the following does:
$(window).load(function() { $('#abc')...} );
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.
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.
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.
I can see that this question has been asked several times, but none of the proposed solutions seem to work for the site I am building, so I am reopening the thread. I am attempting to size an iframe based on the height of it's content. Both the page that contains the iframe and it's source page exist on the same domain.
I have tried the proposed solutions in each of the following threads:
Resize iframe height according to content height in it
Resizing an iframe based on content
I believe that the solutions above are not working because of when the reference to body.clientHeight is made, the browser has not actually determined the height of the document.
Here is the code I am using:
var ifmBlue = document.getElementById("ifmBlue");
ifmBlue.onload = resizeIframe;
function resizeIframe()
{
var ifmBlue = document.getElementById("ifmBluePill");
var ifmDiv = ifmBlue.contentDocument.getElementById("main");
var height = ifmDiv.clientHeight;
ifmBlue.style.height = (ifmBlue.contentDocument.body.scrollHeight || ifmBlue.contentDocument.body.offsetHeight || ifmBlue.contentDocument.body.parentNode.clientHeight || height || 500) + 5 + 'px';
}
If I debug the script using fire debug, the client height of the iframe.contentDocument's main div is 0. Additionally, body.offsetHieght, & body.scrollHeight are 0. However, after the script is finished running, if I inspect the DOM of the HTML iframe element (using fire debug) I can see that the body's clientHeight is 456 and the inner div's clientHeight is 742. This leads me to believe that these values are not yet set when iframe.onload is fired. So, per one of the threads above, I moved the code into the body.onload event handler of the iframe's source page. This solution also did not work.
Any help you can provide is much appreciated.
Thanks,
CJ
DynamicDrive has such a script, which I think does what you're asking for.
There's also a newer version now.
2011 update:
I would strongly recommend using AJAX over something like this, especially considering that a dynamically resizing iframe only works across the same domain.
Even so, it's a bit iffy, so if you absolutely must use AJAX over standard page loading, you really, really should use things like history.pushState (and have standard page loading as a fallback for browsers that don't support it). There's a jQuery plugin which handles this stuff for you, written by a GitHubber, called pjax, which they use only for repo navigation.
you moved the handler? maybe you should move the function to the inner frame as well, so that when you grab height values you reference the body directly rather than frame object... then call a parent.set height function
another trick, call function after settimeout of 10 msecs
i remember I had that problem once but I used IE's getBoundingClientRect() to get height of content, check mozilla developer center for something similar, this is just a hint, i did not research it
on another note, what is ifmBluePill? is it the iframe? or a div inside of it? why do you reference "contentDocument" of a div?
By the way, DynamicDrive improved their script to always resize even if the iframe contents change: http://www.dynamicdrive.com/dynamicindex17/iframessi2.htm
From their page:
This is version II of the original
Iframe SSI script, which like the
original script lets you seamlessly
display external content on your page
via an IFRAME. It does this by
dynamically resizing the IFRAME to be
the height of the page contained
within it, eliminating any possible
IFRAME scrollbars from appearing while
snugly showing the entire external
content. Think of it as SSI (server
side includes) emulated using DHTML!
This script works in both IE5+ and
NS6+, and for other browsers, supports
the option to either completely hide
the iframe in question or display it
using its default height.
Now, this script differs from the
original in that you can load
additional documents* into the IFRAME
even after the page has loaded, and
the IFRAME will dynamically adjust its
height to fit the new document. So use
this script if you need to not only
display external content via the
IFRAME tag, but intend to change this
content after the page has loaded.