34

I've been testing a lot of lazy-loaders for JavaScript and CSS that insert <script> and <link> tags to load files. However the problem is, that <link> tags don't fire onload so it's difficult to detect when they're loaded. The only workaround I found for this is to set display: none; (in the CSS file that is to be loaded) on a dummy element and poll that element to check when it has been set to display: none. But that, apart from being ugly, of course only works for a single CSS file.

So I was wondering; Is there any other way to detect if a CSS file has been loaded?

halfer
  • 18,701
  • 13
  • 79
  • 158
Lukas
  • 7,340
  • 10
  • 51
  • 94
  • 2
    Check this out: https://github.com/jAndreas/Supply – jAndy Dec 20 '10 at 10:10
  • I was hoping for a non-XHR solution so the file I load could be cached and overall it *feels* cleaner to add/remove files instead of loading CSS/JS and inserting/executing it... – Lukas Dec 20 '10 at 10:55
  • It is the only reliable method I'm aware of. Streaming the data to the client and putting it into a `style` tag. – jAndy Dec 20 '10 at 11:32
  • 1
    (Regarding the note in the question, which I have removed, about an answer "at the bottom of the page". Stack Overflow regularly re-orders answers, so whichever answer is at the bottom changes regularly). – halfer Oct 15 '17 at 20:28

9 Answers9

16

edit: It should be noted that browser support for onload events on CSS files has improved since my original answer. It is not fully supported though, so my answer below still has some relevance. Here is a compatibility chart, not sure how legit the source is though.

Ok, I finally found a solution.

This guy http://tugll.tugraz.at/96784/weblog/9080.html inserts link-tags and polls document.styleSheets[index].rules until it is no longer undefined (where index of course is the index of the newly inserted file). Unfortunately his code is buggy and only works with Safari & FF. So I fixed the bugs, added functionality for Opera and Internet Explorer and even added features for adding multiple CSS and JS files and 1 final callback (when all files are loaded) in a sweet and simple lazyloader-function. The result can be found here:

https://github.com/LukasBombach/Lazyloader

Lukas
  • 7,340
  • 10
  • 51
  • 94
4

Worth knowing that now current versions of Chrome and Firefox fire the onload event on links.

var cssFile = document.createElement("link");
cssFile.setAttribute("rel", "stylesheet");
cssFile.setAttribute("type", "text/css");
// Add event listener
cssFile.onload = function(){ console.log('loaded'); }
cssFile.setAttribute("href", 'pathToYour.css');
document.getElementsByTagName("head")[0].appendChild(cssFile);
Ben
  • 1,841
  • 20
  • 24
4

Edit: (Because of the possible not support WebKit)

So I'd rather recommend JQuery LOADER

$("a.button, input.button, button.button").Loader(
{
    url: [
        'core.css',
        'theme.css',
        'button.css'
    ],
    success: function() {
        $(this).button();
    }
});

You can take a look at LazyLoad JavaScript library.

LazyLoad is a tiny (only 1,541 bytes minified), dependency-free JavaScript library that makes it super easy to load external JavaScript and (new in this version) CSS files on demand. It’s ideal for quickly and unobtrusively loading large external scripts and stylesheets either lazily after the rest of the page has finished loading or on demand as needed.

In addition to CSS support, this version of LazyLoad also adds support for parallel loading of multiple resources in browsers that support it. To load multiple resources in parallel, simply pass an array of URLs in a single LazyLoad cal

Daniel Böhmer
  • 12,527
  • 5
  • 31
  • 45
eriksv88
  • 3,260
  • 3
  • 25
  • 49
  • 2
    I wouldn't rely on this: `// Gecko and WebKit don't support the onload event on link nodes, so we just have to finish after a brief delay and hope for the best.` – jAndy Dec 20 '10 at 10:20
  • I tested wonko's lazyload. It fires your onload-callback-funtions instantly (instead of when the css has finished loading) if you use it with css files. – Lukas Dec 20 '10 at 10:27
  • @jAndy: Is it a big problem then?. There is only one line that changes it;) If u use jQuery yue can use ..ready() – eriksv88 Dec 20 '10 at 10:33
  • 1
    @sv88erik: I don't get you. I just copied that comment out of the source which basically tells us, that this script does not provide a solution for the problem at all. – jAndy Dec 20 '10 at 10:37
  • @jAndy: Ahh. I see your point! But it's pretty easy to change with, for example, to use jQyery to get around this. – eriksv88 Dec 20 '10 at 10:43
  • @sv88erik: I wouldn't know how jQuery could help someone out here. – jAndy Dec 20 '10 at 11:15
  • @jAndy It must be said that I did not have very much experience with WebKit or Safari, but know that ready to work on Safari for a colleague of mine has said. But see my update – eriksv88 Dec 20 '10 at 11:21
  • @sv88erik: The jquery plugin also fires callbacks immediately for CSS files. – Lukas Dec 20 '10 at 11:41
3

Try to add a certain CSS rule to the end of your file and wait for the CSS to be applied (check that via JavaScript). After that you can be pretty sure the CSS has been loaded. However I have no experience with that. Just a quick idea might be worth a try.

Daniel Böhmer
  • 12,527
  • 5
  • 31
  • 45
  • The problem with that is, if you have X css files, you need X different css rules, and that is a mess. Also you need to manage a lot of css-filecss-rule relations and you may run out of CSS rules. – Lukas Dec 20 '10 at 10:35
  • I agree with that. The most comfortable way to implement this is probably writing a JS function for loading the CSS file. With the checker rules being named after the CSS filename the JS function can work out the rule itself. I just have no time right now to give you a short example. – Daniel Böhmer Dec 20 '10 at 10:44
2

I wanted to offer some insight into what may have changed.

According to one of the last paragraphs on Mozilla's documentation of <link>, a load event can be attached to a link element in FF versions 9 and up as well as Chrome 19 and up. IE and Safari are not stated. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link

halfer
  • 18,701
  • 13
  • 79
  • 158
Steven Nelson
  • 83
  • 1
  • 7
1

Using a script loader like Supply, this would look like:

supply.listen('text/css', function(payload, filename) {
    switch(filename) {
        case: 'foo.css': {
            // do something
            break;
        }
        case: 'baseball.css': {
            break;
        }
        // ...
    }
});

supply.files({
    stylesheet: [
        'foo.css',
        'baseball.css'
    ]
});

Ref.: SupplyJS

jAndy
  • 212,463
  • 51
  • 293
  • 348
1

About the "load-checking CSS rules" :

If, in your JS script, you append in your head a style tag containing a simple rule like : #loadingCss { display: block; }

Than, you just have to add in all your CSS files something like : #loadingCss { display: none; }

In the head of your doc you append the style tag before the link tag. This way, the CSS rule in the CSS file will be more important (same selector -> same priority, the last one in the doc is the winner).

Than, in your JS script you just have to check #loadingCss visibility. Once you know your CSS file is loaded, you can remove the style tag.

For the next CSS file you want to load, you can add this style tag again at the end of the head element.

This way, with only one rule, you can manage all your CSS files loading. The only problem with this solution : You can't load more than one CSS file at a time. But I'm sure it's possible to find a way to do so.

But anyway, I'm not sure this solution (using a CSS rule to check loading) is a very good solution. Maybe there's other ways to do this.

Nicolas
  • 1,189
  • 1
  • 9
  • 12
1

try this: 1- preload css on head

<link rel="preload" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" as="style" onload="this.rel='stylesheet'"> 
<link rel="preload" href="style.css" as="style" onload="this.rel='stylesheet'"> 

2 - preload js file on footer

    <link rel="preload" as="script" href="https://ajax.googleapis.com/ajax/libs/jquery/2.2.1/jquery.min.js">
    <link rel="preload" as="script" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js">
    <link rel="preload" as="script" href="script.js">

3 - before tag </body> add

<script>        
/**  load defer css - js*/  
// defer css
var cssAll = document.querySelectorAll('[as="style"]');
for(i = 0; i < cssAll.length; i++){
    if(cssAll[i].getAttribute("rel") == "preload"){         
        cssAll[i].setAttribute("rel", "stylesheet");
    }
}
//defer js
var jsAll = document.querySelectorAll('[as="script"]');
if(!window.jQuery)
{
    // load jquery lib
    var script = document.createElement('script');
    script.type = "text/javascript";
    script.src = jsAll[0].getAttribute("href");
    document.getElementsByTagName('head')[0].appendChild(script);
   // load other lib after load jquery
    script.onload = function () {
        for(i = 1; i < jsAll.length; i++){
            var jsNext = document.createElement('script');
            jsNext.type = "text/javascript";
            jsNext.src = jsAll[i].getAttribute("href");
            document.getElementsByTagName('head')[0].appendChild(jsNext);
        }
    }   
}
</script>   

I tested on firefox 57, chrome 61, yandex, not tested on opera and ie

Hung Pham
  • 129
  • 1
  • 2
0

This is by far the simplest and best solution I have found so far and it works perfectly:

var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = css_url;

head.appendChild(link);

var img = document.createElement('img');
document.body.appendChild(img);

img.onerror = img.onload = function() {
    img.onerror = img.onload = null;
    document.body.removeChild(img);
    // do whatever you need to do when css loaded;
};

img.src = css_url;
Frane Poljak
  • 2,137
  • 19
  • 21