Monday, November 19, 2007

Avoid Flash Of Unstyled Content using javascript

Don't hate it when the browser displays ugly HTML for a second, then thinks it twice and reformats it as expected?

A Flash Of Unstyled Content (from now on FOUC) happens when the browser shows unstyled content before applying the CSS style sheet (wikipedia). It used to be associated to old versions of IE but also with accessible interfaces that offer richer javascript alternatives. Consider the following:

  • List menu: the bare HTML should show all options expanded (in case javascript is off, the visitor is a search engine... pick your choice) and use javascript to collapse unselected options.

  • Tabs: same thing, show all tab contents and hide the unselected ones.

  • Any kind of collapsible interface, Blogger for instance: if the connection is slow you can glimpse the collapsed components while the browser downloads the entire document.


Solutions are not straightforward: components that do not yet exist in the DOM tree cannot be hidden using javascript so they must be created (and maybe displayed) before they can be manipulated.

Register a "DOM document loaded" event handler



An example using prototype:

document.observe("dom:loaded", new function() {
$('myDiv').hide();
});


This waits until the document is fully downloaded and will flash if the page is big enough or the connection crappy enough.

Place a <>script> tag after the element



<div id="myDiv">
...
</div>
<script>
$('myDiv').hide();
</script>


Yikes. You are desperate, right? This script requires that the DOM node already exists, and that sometimes means that the script tag must be placed somewhere else (next to the nearest ending DIV tag, for example). This would not work as a generic approach for a JSP tag (TabTag, MenuTag) that does not know how the surrounding HTML looks like.

CSS sleight-of-hand



Create a CSS style on the fly before the affected HTML content:

<script>
document.write('<style type="text/css">.hide-fouc { display:none; }</style>');
document.observe("dom:loaded", function() {
$$('.hide-fouc').invoke('removeClassName', 'hide-fouc');
});

<script>
<div id="myDiv" class="hide-fouc">
...
<div>


A browser with javascript enabled will not see the component, but javascript-less browsers will. The CSS class is removed after the entire document has been loaded to allow the element to reappear later.

Note that this snippet should be included inside the HTML (not as a external javascript file) and that you should achieve better performance by removing the display:none attribute from the CSS class instead of using a selector, but as said before I am a lazy bastard.

9 comments:

  1. the invoke removeClassName doesn't seem to be working for my mac firefox...

    i am able to set display to none, but then it is permanent, and I cannot toggle display back on afterwards with js/ css/ etc.//

    ReplyDelete
  2. You need to remove the hide-fouc class from every element. If you need more help, please email me at gmail.com with your concrete code. If you did not modify anything, this example should work.

    ReplyDelete
  3. It doesn't work for me on Firefox 3 in XP either. As a side note the textbox used for comments doesn't support the arrows keys, home/end etc.?

    ReplyDelete
  4. And now I can use arrow keys and thereby easily actually write something in here.

    ReplyDelete
  5. Ok, this works for me on Firefox 3 in XP:

    At the top of body:
    (snipped)

    And once more arrows, home etc. are not usable. I'm thankful for the idea, but this tesxtfield is really useless. Sorry, can't figure out how to actually write the tags here and since I can't use standard tools for editing the text (arrows, home etc.) it's way too much time consuming. I'll add the code on the wikipedia talk page for those interested.

    ReplyDelete
  6. You can also use javascript to write a css class to the html tag and then use css specific styles for the content you want to hide, it makes it slightly cleaner.
    To see a full explanation visit here:

    Avoiding FOUC

    ReplyDelete
  7. Hi Simon,

    I like your approach. Adding the class to the HTML tag looks much cleaner, and makes IMHO a much better implementation. Kudos!

    ReplyDelete
  8. After revisiting this solution, it seems possible only because the HTML 5 includes the class attribute in the HTML tag spec.

    ReplyDelete
  9. Another implementation that (imho) beats the alternatives explained here is the recommendation by LABjs (search for "LABjs & User-Experience").

    ReplyDelete

Something on your mind?

Note: Only a member of this blog may post a comment.