32

I am trying to load a css file dynamically using javascript and cannot use any other js library (eg jQuery).

The css file loads but I can't seem to get a callback to work for it. Below is the code I am using

var callbackFunc = function(){
    console.log('file loaded');     
};
var head = document.getElementsByTagName( "head" )[0];
var fileref=document.createElement("link");
    fileref.setAttribute("rel", "stylesheet");
    fileref.setAttribute("type", "text/css");
    fileref.setAttribute("href", url);

    fileref.onload  = callbackFunc;
    head.insertBefore( fileref, head.firstChild );

Using the following code to add a script tag to load a js file works and fires a callback:

var callbackFunc = function(){
    console.log('file loaded');     
};

var script = document.createElement("script");

script.setAttribute("src",url);
script.setAttribute("type","text/javascript");

script.onload  = callbackFunc ;

head.insertBefore( script, head.firstChild );

Am I doing something wrong here? Any other method that can help me achieve this would be much appreciated?

mVChr
  • 49,587
  • 11
  • 107
  • 104
U.Ahmad
  • 618
  • 1
  • 6
  • 8
  • 1
    I'd suggest that if there are libraries that you **don't** want to use, then don't tag them in the question. Tagging them nonetheless attracts the [wrong] kind of developer for your question. – Richard Neil Ilagan Apr 04 '11 at 11:19
  • Very much a duplicate of https://stackoverflow.com/questions/3078584/link-element-onload. Whether jQuery is used or not is irrelevant (jQuery code can be easily converted to vanilla). The main issue is that, until very recently, WebKit did not support the `load` event for `link` elements. And that's a large and still very prevalant number of mobile phones, both iOS and Android. (Unusally, IE supports the event since IE7 if not earlier.) – Jake Oct 25 '17 at 23:00

7 Answers7

38

Unfortunately there is no onload support for stylesheets in most modern browsers. There is a solution I found with a little Googling.

Cited from: http://thudjs.tumblr.com/post/637855087/stylesheet-onload-or-lack-thereof

The basics

The most basic implementation of this can be done in a measely 30 lines of — framework independent — JavaScript code:

function loadStyleSheet( path, fn, scope ) {
   var head = document.getElementsByTagName( 'head' )[0], // reference to document.head for appending/ removing link nodes
       link = document.createElement( 'link' );           // create the link node
   link.setAttribute( 'href', path );
   link.setAttribute( 'rel', 'stylesheet' );
   link.setAttribute( 'type', 'text/css' );

   var sheet, cssRules;
// get the correct properties to check for depending on the browser
   if ( 'sheet' in link ) {
      sheet = 'sheet'; cssRules = 'cssRules';
   }
   else {
      sheet = 'styleSheet'; cssRules = 'rules';
   }

   var interval_id = setInterval( function() {                     // start checking whether the style sheet has successfully loaded
          try {
             if ( link[sheet] && link[sheet][cssRules].length ) { // SUCCESS! our style sheet has loaded
                clearInterval( interval_id );                      // clear the counters
                clearTimeout( timeout_id );
                fn.call( scope || window, true, link );           // fire the callback with success == true
             }
          } catch( e ) {} finally {}
       }, 10 ),                                                   // how often to check if the stylesheet is loaded
       timeout_id = setTimeout( function() {       // start counting down till fail
          clearInterval( interval_id );             // clear the counters
          clearTimeout( timeout_id );
          head.removeChild( link );                // since the style sheet didn't load, remove the link node from the DOM
          fn.call( scope || window, false, link ); // fire the callback with success == false
       }, 15000 );                                 // how long to wait before failing

   head.appendChild( link );  // insert the link node into the DOM and start loading the style sheet

   return link; // return the link node;
}

This would allow you to load a style sheet with an onload callback function like this:

loadStyleSheet( '/path/to/my/stylesheet.css', function( success, link ) {
   if ( success ) {
      // code to execute if the style sheet was loaded successfully
   }
   else {
      // code to execute if the style sheet failed to successfully
   }
} );

Or if you want to your callback to maintain its scope/ context, you could do something kind of like this:

loadStyleSheet( '/path/to/my/stylesheet.css', this.onComplete, this );
Dmitry Yaremenko
  • 2,542
  • 20
  • 31
mVChr
  • 49,587
  • 11
  • 107
  • 104
  • This will cause IE7 or later to stop script automatically. and so no callback will fire. – Farzin Zaker Apr 04 '11 at 11:52
  • Thanks I also ran into another blog post suggesting the same [here](http://www.phpied.com/when-is-a-stylesheet-really-loaded/). Basically to use a timer to check all scrips in the head and see if the required script is loaded or not. – U.Ahmad Apr 04 '11 at 11:58
  • when i try to execute it, it always returns false, although the stylesheet gets loaded fine. On debugging I found that `link[sheet]` always returns null. do you have any idea about that – Tirtha Jan 24 '12 at 12:23
  • very helpful, fixed my issue I was facing only in Safari browser. – neelmeg Aug 20 '12 at 17:48
  • option #2 on his page seems like a nicer solution: "Use AJAX to load in the stylesheet’s content, create a style node and insert the XHR responseText into the style node." – jedierikb Feb 27 '15 at 16:46
  • If the stylesheet fails to load for whatever reason, wouldn't this cause the interval to run forever? – Benjamin Solum Jun 23 '15 at 18:20
  • You could do it with a reinitiating `setTimeout` and an incrementing `tries` counter that bails out if `tries > maxTries` if you're concerned about this. – mVChr Jun 23 '15 at 18:32
  • @Tirtha it seems this method only works for local css, not cross-domain (eg jquery-ui.css etc will need a fallback) – AwokeKnowing Aug 11 '15 at 15:09
  • This breaks IE11 because of the "finally {}". That should probably be removed especially since it isn't doing anything? – Nick Steele Apr 18 '19 at 18:23
9

This vanilla JS approach works in all modern browsers:

let loadStyle = function(url) {
  return new Promise((resolve, reject) => {
    let link    = document.createElement('link');
    link.type   = 'text/css';
    link.rel    = 'stylesheet';
    link.onload = () => { resolve(); console.log('style has loaded'); };
    link.href   = url;

    let headScript = document.querySelector('script');
    headScript.parentNode.insertBefore(link, headScript);
  });
};

// works in IE 10, 11 and Safari/Chrome/Firefox/Edge
// add an ES6 polyfill for the Promise (or rewrite to use a callback)
sandstrom
  • 14,554
  • 7
  • 65
  • 62
  • 1
    Works perfect and that ES6 gets along fine with my Angular typescript app! – Dunos Mar 15 '17 at 11:46
  • Would not have worked in iOS, Android, Chrome or Safari when question was posted, even with ES6 Polyfill, and still won't work in those browsers on systems not patched up to date (e.g. very many users phones) - WebKit has only very recently added support for the `load` event on `link` elements. – Jake Oct 26 '17 at 08:15
6

Some time ago i made a library for this, it's called Dysel, i hope it helps

Example: https://jsfiddle.net/sunrising/qk0ybtnb/

var googleFont = 'https://fonts.googleapis.com/css?family=Lobster';
var jquery = 'https://code.jquery.com/jquery.js';
var bootstrapCss = 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css';
var bootstrapJs = 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js';
var smokeCss = 'https://rawgit.com/alfredobarron/smoke/master/dist/css/smoke.min.css';
var smokeJs = 'https://rawgit.com/alfredobarron/smoke/master/dist/js/smoke.min.js';

// push links into an array in the correct order
var extRes = [];
extRes.push(googleFont);
extRes.push(bootstrapCss);
extRes.push(smokeCss);
extRes.push(jquery);
extRes.push(bootstrapJs);
extRes.push(smokeJs);

// let this happen
dysel({
  links: extRes,
  callback: function() {
    alert('everything is now loaded, this is awesome!');
  }, // optional
  nocache: false, // optional
  debug: false // optional
});
Alessandro Annini
  • 1,531
  • 2
  • 17
  • 32
5

You can make an empty css link in your html file and give the link an ID. e.g

<link id="stylesheet_css" rel="stylesheet" type="text/css" href="css/dummy.css?"/>

then call it with ID name and change the 'href' attribute

thecodeparadox
  • 86,271
  • 21
  • 138
  • 164
  • I don't have access to the html of the page. I can only edit my javascript that the 3rd party html page is loading. So the solution needs to be javascript based. But thanks for the idea. – U.Ahmad Apr 04 '11 at 11:44
1

yepnope.js can load CSS and run a callback on completion. e.g.

yepnope([{
  load: "styles.css",
  complete: function() {
    console.log("oooooo. shiny!");
  }
}]);
1

Here's how we do it. By using "requestAnimationFrame" (or fallback to simple "load" event if its not avail).

By the way, this is the way Google recommends in their "page-speed" manual: https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery

<script>
    function LoadCssFile(cssPath) {
        var l = document.createElement('link'); l.rel = 'stylesheet'; l.href = cssPath;
        var h = document.getElementsByTagName('head')[0]; h.parentNode.insertBefore(l, h);
    }
    var cb = function() {
        LoadCssFile('file1.css');
        LoadCssFile('file2.css');
    };
    var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
    if (raf) raf(cb);
    else window.addEventListener('load', cb);
</script>
Serge Shultz
  • 5,888
  • 3
  • 27
  • 17
  • 1
    If I understand the answer it isn't related to what was asked for. It loads CSS file when PAGE is loaded. The question was how to call a function when CSS file is loaded. – oriadam Dec 31 '15 at 00:08
0

New answer to an old question:

You can simply request the text of the CSS file with AJAX and put it in a <style> tag. When the styles have been appended to the DOM they are available immediately.

Here's a script I came up with:

/**
 * Given a URL for a JS or CSS file, this function will
 * load the asset and return a Promise which will reject
 * on error or resolve when the asset is loaded.
 */
function loadAsset(url){
  return new Promise(async (resolve, reject)=>{
    var asset;
    if(url.trim().substr(-3).toLowerCase() === '.js'){
      asset = document.createElement('script');
      asset.addEventListener('load', resolve);
      asset.addEventListener('error', reject);
      document.head.appendChild(asset);
      asset.setAttribute('src', url);
    }else{
      var styles = await fetch(url)
        .then(c=>c.text())
        .catch(reject);
      asset = document.createElement('style');
      asset.appendChild(document.createTextNode(styles));
      document.head.appendChild(asset);
      resolve();
    }
  });
}
I wrestled a bear once.
  • 22,983
  • 19
  • 69
  • 116