javascript: capturing load event on LINK

i’m trying to attach an event handler to the load event of a link tag, to execute some code after a stylesheet has loaded.

new_element = document.createElement('link');
new_element.type = 'text/css';
new_element.rel = 'stylesheet';
new_element.href = 'http://domain.tld/file.css';
new_element.addEventListener('load', function() { alert('foo'); }, false);
document.getElementsByTagName('head')[0].appendChild(new_element)

i have tried onreadystatechange as well

new_element.onreadystatechange = function() { alert('foo'); }

unfortunately neither approach results in an alert being triggered..
Furthermore, new_element.onload is null after registering a handler for the ‘load’ event with addEventListener.. is that normal?

thanks,
andrew

ps: i may not use any framework in solving this

10 thoughts on “javascript: capturing load event on LINK”

  1. All credit goes to Tobiasz up above, but here’s a little expansion on what he said:

    function _cssIsLoaded(cssStylesheet) {
        var cssLoaded = 0;
        try {
            if ( cssStylesheet.sheet && cssStylesheet.sheet.cssRules.length > 0 )
                cssLoaded = 1;
            else if ( cssStylesheet.styleSheet && cssStylesheet.styleSheet.cssText.length > 0 )
                cssLoaded = 1;
            else if ( cssStylesheet.innerHTML && cssStylesheet.innerHTML.length > 0 )
                cssLoaded = 1;
            }
            catch(ex){ }
    
            if(cssLoaded) {
                // your css is loaded! Do work!
                // I'd recommend having listeners subscribe to cssLoaded event, 
                // and then here you can emit the event (ie. EventManager.emit('cssLoaded');
            } else {
                // I'm using underscore library, but setTimeout would work too
                // You basically just need to call the function again in say, 50 ms
                _.delay(_.bind(this._cssIsLoaded, this), 50, cssStylesheet);
            }
    }
    

    You’d call it with something like (using jquery):

    var link = $("<link>");
    link.attr({
        type: 'text/css',
        rel: 'stylesheet',
        href: sheet
    });
    
    $("head").append(link);
    // link.get(0), because you want the actual element, not jQuery-wrapped element
    self._cssIsLoaded(link.get(0));
    
    Reply
  2. Even if you add an inline:

    <link rel="stylesheet" type="text/css" href="foo.css" onload="alert('xxx')"/>
    

    It won’t fire in FireFox as there isn’t an onload event for link elements. (It will work in IE)

    Reply
  3. For CSS stylesheets (not LINK elements in general) i’m using manual interval, by poking it’s rules length. It works crossbrowser (AFAIT).

    try {
      if ( cssStylesheet.sheet && cssStylesheet.sheet.cssRules.length > 0 )
        cssLoaded = 1;
      else if ( cssStylesheet.styleSheet && cssStylesheet.styleSheet.cssText.length > 0 )
        cssLoaded = 1;
      else if ( cssStylesheet.innerHTML && cssStylesheet.innerHTML.length > 0 )
        cssLoaded = 1;
    }
    catch(ex){}
    

    In code above, the cssStylesheet is DOMElement.

    Reply
  4. Here’s what is, in my opinion, a better solution for this issue that uses the IMG tag and its onerror event. This method will do the job without looping, doing contorted style observance, or loading files in iframes, etc. This solution fires correctly when the file is loads, and right away if the file is already cached (which is ironically better than how most DOM load events handle cached assets). Here’s a post on my blog that explains the method – Back Alley Coder post – I just got tired of this not having a legit solution, enjoy!

    var loadCSS = function(url, callback){
        var link = document.createElement('link');
            link.type = 'text/css';
            link.rel = 'stylesheet';
            link.href = url;
    
        document.getElementsByTagName('head')[0].appendChild(link);
    
        var img = document.createElement('img');
            img.onerror = function(){
                if(callback) callback(link);
            }
            img.src = url;
    }
    
    Reply
  5. This method suits me better.

    // Create a <link> and set it in an HTML node
    const 
    $link = document.createElement("link");
    $link.setAttribute('rel', 'stylesheet');
    $link.setAttribute('href', '/build/style/test.css');
    document.head.appendChild($link);
    
    // Event about load
    onloadCSS($link).then(function(url) {
        console.log('CSS loaded: ' + url);
    }).catch(function(e) {
        console.log('Failed to load CSS');
    });
    
    function onloadCSS($link){
        return new Promise(function(resolve, reject) {
            const 
            options = {
                headers: ({'Accept': 'text/css,*/*;q=0.1'})
            }, 
            url = $link.getAttribute('href');
    
            fetch(url, options).then(function(response) {
                if(response.status === 200){
                    return resolve(url);
                }
    
                return reject({status: response.status});
            }).catch(function(e) {
                return reject(e);
            });      
        });
    }
    Reply
  6. Nice shot Matt. I’ve created this helper with your comment.

    var CSSload = function(link, callback) {
        var cssLoaded = false;
        try{
            if ( link.sheet && link.sheet.cssRules.length > 0 ){
                cssLoaded = true;
            }else if ( link.styleSheet && link.styleSheet.cssText.length > 0 ){
                cssLoaded = true;
            }else if ( link.innerHTML && link.innerHTML.length > 0 ){
                cssLoaded = true;
            }
        }
        catch(ex){ }
        if ( cssLoaded ){
            callback();
        }else{
            setTimeout(function(){
                CSSload(link);
            }, 100);
        }
    };
    

    Usage:

    var $link = $('<link rel="stylesheet" media="screen" href="file.css"/>');
    $('head').append($link);
    CSSload($link.get(0), function(){
      // do something onLoad
    });
    
    Reply
  7. The link.innerHTML, link.styleSheet and cssRules are all good approaches, but they do not work for stylesheets that belong to a domain outside of the origin domain (so cross-site stylesheet loading fails). This can be pretty unexpected when a subdomain vs a domain is used (www for example) or a static CNS is used. And its pretty annoying since elements have no same-origin restriction.

    Here’s a solution that that uses the onload method for browsers that support it (IE and Opera), but then uses a timed interval for browsers that do not and compares the ownerNode and owningElement nodes to check to see when the stylesheet has made its way into the DOM.

    http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html

    Reply
  8. This function has held up on all browsers as well as in both cross domain and same domain situations, also this handles the injection of javascripts as well as stylesheets.

    function loadScript(src, type, callback_fn) {
        var loaded = false, scrpt, img;
        if(type === 'script') {
            scrpt = document.createElement('script');
            scrpt.setAttribute('type', 'text/javascript')
            scrpt.setAttribute('src', src);
    
        } else if(type === 'css') {
            scrpt = document.createElement('link')
            scrpt.setAttribute('rel', 'stylesheet')
            scrpt.setAttribute('type', 'text/css')
            scrpt.setAttribute('href', src);
        }
        document.getElementsByTagName('head')[0].appendChild(scrpt);
    
        scrpt.onreadystatechange = function(){
            if (this.readyState === 'complete' || this.readyState === 'loaded') {
                if(loaded === false) {
                    callback_fn();
                }
                loaded = true;
            }
        };
    
        scrpt.onload = function() {
            if(loaded === false) {
                callback_fn();
            }
            loaded = true;
        };
    
        img = document.createElement('img');
        img.onerror = function(){
            if(loaded === false) {
                callback_fn();
            }
            loaded = true;
        }
        img.src = src;
    };
    
    Reply
  9. E.g. Android browser doesn’t support “onload” / “onreadystatechange” events for element: http://pieisgood.org/test/script-link-events/
    But it returns:

    "onload" in link === true
    

    So, my solution is to detect Android browser from userAgent and then wait for some special css rule in your stylesheet (e.g., reset for “body” margins).
    If it’s not Android browser and it supports “onload” event- we will use it:

    var userAgent = navigator.userAgent,
        iChromeBrowser = /CriOS|Chrome/.test(userAgent),
        isAndroidBrowser = /Mozilla\/5.0/.test(userAgent) && /Android/.test(userAgent) && /AppleWebKit/.test(userAgent) && !iChromeBrowser; 
    
    addCssLink('PATH/NAME.css', function(){
        console.log('css is loaded');
    });
    
    function addCssLink(href, onload) {
        var css = document.createElement("link");
        css.setAttribute("rel", "stylesheet");
        css.setAttribute("type", "text/css");
        css.setAttribute("href", href);
        document.head.appendChild(css);
        if (onload) {
            if (isAndroidBrowser || !("onload" in css)) {
                waitForCss({
                    success: onload
                });
            } else {
                css.onload = onload;
            }
        }
    }
    
    // We will check for css reset for "body" element- if success-> than css is loaded
    function waitForCss(params) {
        var maxWaitTime = 1000,
            stepTime = 50,
            alreadyWaitedTime = 0;
    
        function nextStep() {
            var startTime = +new Date(),
                endTime;
    
            setTimeout(function () {
                endTime = +new Date();
                alreadyWaitedTime += (endTime - startTime);
                if (alreadyWaitedTime >= maxWaitTime) {
                    params.fail && params.fail();
                } else {
                    // check for style- if no- revoke timer
                    if (window.getComputedStyle(document.body).marginTop === '0px') {
                        params.success();
                    } else {
                        nextStep();
                    }
                }
            }, stepTime);
        }
    
        nextStep();
    }
    

    Demo: http://codepen.io/malyw/pen/AuCtH

    Reply

Leave a Comment