2

I've got a custom slideshow page with hundreds of large pictures in markup. In addition, there is an adjustment to the layout that can't be done with CSS alone, and requires javascript. That adjustment depends on the computed CSS styles of a couple page elements.

Here's the issue: getting a computed CSS style on (document).ready doesn't seem to work. That makes perfect sense, because layout/paint haven't occurred yet when the DOM is merely registered.

(window).load is the obvious answer here. But the waiting period for (window).load is maddeningly long in this case, because it takes literally 30 seconds (longer on slow connections!) for all the 100+ large images to be downloaded. That's fine in principle for the slideshow, because the slideshow starts being usable once the first two images are loaded, long before all the images are loaded. The thing is, I'd like the scripted CSS layout correction to occur at that point as well.

To check for imgs being loaded, I'm use imageLoaded, which is great, and gets around the issues with jquery's load event when used with imgs. But how do I check for "load"-like events (and the availability of computed styles) on a page element, for example a div? (I realize divs don't fire load events.) In principle, is there a reliable cross-browser DOM event in between (document).ready and (window).load that I can listen for on a particular set of elements, to get computed styles? Or am I forced to choose between these two temporal extremes?

6
  • Would you be able to put off adding the slideshow images until after window.load? That would ensure that the rest of your page is done loading and useable before you attempt to load in hundreds of large images. Commented Sep 26, 2012 at 18:25
  • @Kevin That's definitely an idea, but it's something of a last resort, because it complicates things enormously. I was trying to keep everything in markup, because it's so simple to edit and maintain, and it degrades effortlessly when js is disabled. Commented Sep 26, 2012 at 18:41
  • 1
    It could be as simple as giving it a class with display:none; that gets removed by javascript, then using noscript tags to override it with display:block;. Commented Sep 26, 2012 at 18:43
  • @Kevin That's clever. I'm using modernizr "js" and "no-js" classes already, so that would be a cinch. But it hinges of course on browsers not downloading assets that are set to display:none. And last I checked, browsers still download assets set to display: none. So window.load wouldn't actually fire any sooner, would it? Commented Sep 26, 2012 at 19:03
  • I was under the impression that browsers did not load images that were hidden. That's why preloaders are needed for menus that have hover images that aren't using image sprites. Commented Sep 26, 2012 at 19:24

3 Answers 3

3

On CSS load

Source: https://stackoverflow.com/a/12570580/1292652

Add a unique reference styles to the CSS files you want to check for like so:

#ensure-cssload-0 {
  display: none;
}

Then, use a JS function, cssLoad(), to repeatedly check whether the CSS has been downloaded (not sure if this is significantly different from when the CSS is actually painted/rendered):

var onCssLoad = function (options, callback) {
    var body = $("body");
    var div = document.createElement(constants.TAG_DIV);
    for (var key in options) {
        if (options.hasOwnProperty(key)) {
            if (key.toLowerCase() === "css") {
                continue;
            }
            div[key] = options[key];
        }
    }

    var css = options.css;
    if (css) {
        body.appendChild(div);
        var handle = -1;
        handle = window.setInterval(function () {
            var match = true;
            for (var key in css) {
                if (css.hasOwnProperty(key)) {
                    match = match && utils.getStyle(div, key) === css[key];
                }
            }

            if (match === true) {
                window.clearTimeout(handle);
                body.removeChild(div);
                callback();
            }
        }, 100);
    }
}

You can use it as:

onCssLoad({
    "id": <insert element CSS applies to>,
     css: <insert sample CSS styles>
}, function () {
    console.log("CSS loaded, you can show the slideshow now :)");
});


On CSS computation

Source: http://atomicrobotdesign.com/blog/javascript/get-the-style-property-of-an-element-using-javascript/

The previous solution only told if a CSS file had been 'downloaded', while in your question you mention you need to know when a style for an element has been 'computed' or 'rendered' or 'painted'. I hope this might resolve that.

Add a unique reference styles to the CSS files you want to check for like so:

#ensure-cssload-0 {
  ignored-property: 'computed';
}

Now, use getPropertyValue and getComputedStyle to check for the CSS:

function getStyle(elem, prop) {
    return window.getComputedStyle(elem, null).getPropertyValue(prop);
}

Now, just use a while loop with a callback:

function checkStyle(elem, prop, callback) {
    while ( getStyle(elem, prop) !== 'computed' ) {
        // see explanation below
    }
    callback()
}

Since JavaScript doesn't have a pass statement (like in Python), we use an empty code block.

If you want to retrieve all the CSS styles applied to an element (warning: this will also show inherited styles), check out this SO answer.


Avoiding the while() loop

Using while loops to check for anything is usually NOT recommended as it hangs uo the browser doesn't let the JavaScript thread do anyting else (all browsers nowadays are multithreaded, see this comic).

Here's Chuck suggested, this solution is much better:

function checkStyle(elem, prop, callback) {
    if ( getStyle(elem, prop) !== 'computed' ) {
        // see explanation below
        window.setTimeout( function() {checkStyle(elem, prop, callback);}, 100 )
    } else {
        callback()
    }
}

This is much better. Now the function asynchronously checks every 100ms (you can change this value if you want). The wrapping of the function while setting the timeout was necessary as setTimeout() doesn't allow passing of any arguments yet.

Hope this helped. See the source code of this post for some more links...

Sign up to request clarification or add additional context in comments.

3 Comments

You can already pass arguments from the current scope to setTimeout() ...but with such a weird syntax it's often impractical. For example to pass argument <foo> from the current scope: foo='bar'; setTimeout('myfunc('+foo+')', 100); The arguments are evaluated immediately and in the current scope, not at the time myfunc(...) is executed nor in the execution scope.
@ChuckKollars Why? Why would they do such a thing? The language designers are evil! Even though your previous solution was longer, it was less arcane and more readable/Googleable...
@ChuckKollars Thanks, super-interesting. What is that double plus sign +foo+ syntax/operator called? Do you have a good documented reference for it?
1

You can use DOMContentLoaded event. It fires when dom tree is ready and styles applyed. window.load fires, when all images and scripts loaded.

if you using jQuery, you can use this:

$(function(){
 // this fn not waiting for images
  $('.selector-class')[0].style ;// this is your computed style OR...
  $('.selector-class').css(); // json representation of style;
});

if you have no jQuery on page ... in modern browsers you can use this code:

document.addEventListener( "DOMContentLoaded", function(){
  var computedStyle = document.querySelector('#yourElementId').style ; 
})

YatharthROCK Addedd very precission solution below.

5 Comments

Thanks, but you're missing the question slightly. DOMContentLoaded (same as jQuery window.load on modern browsers) takes way too long on my page, over thirty seconds. It's unusable. That's the whole premise of the question.
DOMContentLoaded doesn't wait for stylesheets to be loaded. developer.mozilla.org/en-US/docs/DOM/DOM_event_reference/…
Right, but it does wait for images, which in this case is way too long.
@Maxim Cool, didn't know it was so easy with jQuery. It really fixes a lot of problems with the horrible DOM API...
@Maxim. Your revised answer using (document).ready doesn't work either, for all the reasons outline in my original question. Computed styles are not available at (document).ready, because layout has not been performed yet. A paragraph with no explicit CSS height will not have a computed height at document.ready. Every once in a while it will work as a fluke, sometimes on reloaded pages, in some browsers, and depending on the nature of the page, but it doesn't work at all reliably.
0

My other answer only told you when a CSS file had been downloaded, while in your question you mention you need to know when a style for an element has been computed or rendered or painted- has been updated.

I hope this addresses the issue (I didn't want to merge this with the other answer as I felt it attacked a different angle...) I changed my mind, see the edit.

Source: http://atomicrobotdesign.com/blog/javascript/get-the-style-property-of-an-element-using-javascript/

Add a unique reference styles to the CSS files you want to check for like so:

#ensure-cssload-0 {
  ignored-property: 'computed';
}

Now, use getPropertyValue and getComputedStyle to check for the CSS:

function getStyle(elem, prop) {
    return window.getComputedStyle(elem, null).getPropertyValue(prop);
}

Now, just use a while loop with a callback:

function checkStyle(elem, prop, callback) {
    while ( getStyle(elem, prop) !== 'computed' ) {
        // see explanation below
    }
    callback()
}

Since JavaScript doesn't have a pass statement (like in Python), we use an empty code block.

If you want to retrieve all the CSS styles applied to an element (warning: this will also show inherited styles), check out this SO answer

7 Comments

Thanks--but can you explain the empty while block? I've seen this kind of code structure before but I honestly don't get it. :)
@Ben As long as the condition fails (i.e., the CSS hasn't been computed), the while block executrs the code inside it (read: doesn't do anything). As soon as the condition is met (i.e., the CSS has been computed), the control flow can move on to execute the callback. Thus: whatever code is in the callback function gets executed right after the CSS is computed. Did that help?
Okay, so it's kind of like an event listener. The problem that I see
oops... Okay, so the goal is to create something like an event listener. But doesn't the false while loop totally block execution? It seems like when the browser hits this while loop, it's going to go in circles, checking and rechecking, blocking the UI thread until the style returns as computed. Seems like bad architecture. How could I do this asychronously? Maybe I'm misunderstanding? Thanks so much...
@Ben Exactly. That's why I wasn't so enthusiastic about advocating this technique. From my other answer: "I don't recommend this for the same reason why you shouldn't use a while loop to implement sleep() (it hangs up the browser), but if you must, then here it goes:"
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.