Using dynamic CSS to hide content before page load

When you create an unobtrusive DOM script that initially hides content you will very likely experience a flash of visible content before it gets hidden. At the root of this issue lies the window.onload event which only executes after the whole document, including all page assets like images and objects, is loaded.

The original solutions

In Unobtrusive show/hide behavior reloaded I offered a solution to overcome this using a piece of JavaScript in the head of your HTML file which includes a CSS file that sets the initial state of your elements:

<script type="text/javascript">
if (document.getElementById) { // include all feature tests needed
                               // for your DOM script
    document.write('<link rel="stylesheet" type="text/css" href="hide.css" />');
    window.onload = myUnobtrusiveBehavior;
}

function myUnobtrusiveBehavior() {
    document.getElementById("el").style.display = "none"; // fallback
    // ...
}
</script>

And a stylesheet named hide.css that includes:

#el {display:none;}

Although this solution works fine as it is, it still can be improved upon a few points. First of all it relies on the existence of an external CSS file, which also needs to be maintained separately. We could easily solve this by using:

<script type="text/javascript">
if (document.getElementById) { // include all feature tests needed
                               // for your DOM script
    document.write('<style type="text/css">#el {display:none;}</style>');
    window.onload = myUnobtrusiveBehavior;
}

function myUnobtrusiveBehavior() {
    document.getElementById("el").style.display = "none"; // fallback
    // ...
}
</script>

However, this solution still doesn't work for XHTML pages that use MIME type application/xhtml+xml, because it uses document.write.

Introducing dynamicCSS.js

Working on UFO I created a generic solution using dynamic CSS to hide and show elements. For this article I have rewritten this solution into a set of functions, that can be reused for multiple purposes: dynamicCSS.js. The script is free and is licensed under the CC-GNU LGPL.

DynamicCSS.js consists of four utility functions. The first function is called createStyleRule(selector, declaration) and can be used to dynamically create style rules. In the context of this article, its most important feature is that it can be called before a page is loaded. Please note that the function uses a code fork to make things work in Internet Explorer/Win and that it doesn't support Internet Explorer/Mac:

function createStyleRule(selector, declaration) {
    if (!document.getElementsByTagName ||
      !(document.createElement || document.createElementNS)) return;
    var agt = navigator.userAgent.toLowerCase();
    var is_ie = ((agt.indexOf("msie") != -1) &&  (agt.indexOf("opera") == -1));
    var is_iewin = (is_ie &&  (agt.indexOf("win") != -1));
    var is_iemac = (is_ie &&  (agt.indexOf("mac") != -1));
    if (is_iemac) return; // script doesn't work properly in IE/Mac
    var head = document.getElementsByTagName("head")[0]; 
    var style = (typeof document.createElementNS != "undefined") ?
      document.createElementNS("http://www.w3.org/1999/xhtml", "style") :
      document.createElement("style");
    if (!is_iewin) {
        var styleRule = document.createTextNode(selector + " {" + declaration + "}");
	    style.appendChild(styleRule); // bugs in IE/Win
    }
	style.setAttribute("type", "text/css");
    style.setAttribute("media", "screen"); 
    head.appendChild(style);
    if (is_iewin &&  document.styleSheets &&  document.styleSheets.length > 0) {
        var lastStyle = document.styleSheets[document.styleSheets.length - 1];
        if (typeof lastStyle.addRule == "object") {
            lastStyle.addRule(selector, declaration);
        }
    }
}

Once a page is loaded you can use one of the following three DOM manipulation functions:

function setElementStyleById(id, propertyName, propertyValue) {
    if (!document.getElementById) return;
    var el = document.getElementById(id);
    if (el) el.style[propertyName] = propertyValue;
}

function setElementStyle(element, propertyName, propertyValue) {
    if (!document.getElementsByTagName) return;
    var el = document.getElementsByTagName(element);
    for (var i = 0; i < el.length; i++) {
        el[i].style[propertyName] = propertyValue;
    }
}

function setElementStyleByClassName(cl, propertyName, propertyValue) {
    if (!document.getElementsByTagName) return;
    var re = new RegExp("(^| )" + cl + "( |$)");
    var el = document.all ? document.all :
      document.getElementsByTagName("body")[0].getElementsByTagName("*");
      // fix for IE5.x
    for (var i = 0; i < el.length; i++) {
        if (el[i].className &&  el[i].className.match(re)) {
            el[i].style[propertyName] = propertyValue;
        }
    }
}

How to use dynamicCSS.js

With these functions in place, we are able to rewrite our original code without having to suffer of any of the negative points mentioned before:

<script type="text/javascript" src="dynamicCSS.js"></script>
<script type="text/javascript">
if (document.getElementById) { // include all feature tests needed
                               // for your DOM script
    createStyleRule("#el", "display:none;");
    window.onload = myUnobtrusiveBehavior;
}

function myUnobtrusiveBehavior() {
    setElementStyleById("el", "display", "none"); // fallback for IE5/Mac
    // ...
</script>

Click here to see this solution in action (Please ensure to use a 'hard reload' or to clear your cache every time you retest the examples). Besides calling elements by their id, you can also hide/show elements directly or toggle elements by their class name. You could even reuse the createStyleRule function to do all the toggling work, however this doesn't work in Internet Explorer/Mac and you may ask yourself how ethical it is to keep on adding new style rules, especially in the case of toggleable elements.

Please note that you can either use the display or the visibility style property to hide and show content. Their main difference is that display entirely hides the element and collapses the space it takes, while visibility still reserves an element's space in a layout while the element is hidden.

Related articles

DOMContentLoaded/defer

Timers

Comments

Any comments, bugs or suggestions, please send me an email.

Metadata

Linked by