jQuery: loading css on demand + callback if done

2019-01-17 03:22发布

问题:

I want to load CSS files on demand (by eg. running an XML HTTP request which returns the CSS files to be loaded) for example style1.css, style2.css ..

So is there a way in jQuery (or a plugin) to this?

  • bulk-loading several files + adding all those CSS-files into the dom
  • when finished loading: firing a callback (like alerting "all stylesheets are finished loaded!");

the idea is: loading html via xmlhttp, loading +adding required css-files, then - after anything is finished, display that html.

any idea?

Thanx!

回答1:

How to load multiple CSS files with callback as requested
Please note without xdomain permissions, $.get will only load local files

WORKING DEMO

$.extend({
    getCss: function(urls, callback, nocache){
        if (typeof nocache=='undefined') nocache=false; // default don't refresh
        $.when(
            $.each(urls, function(i, url){
                if (nocache) url += '?_ts=' + new Date().getTime(); // refresh? 
                $.ajax({
                    url: url,
                    cache: true, // set this to false if each CSS file 
                                 // must refresh and not use a cached version
                    success: function(){
                        $('<link>', {rel:'stylesheet', type:'text/css', 'href':url}).appendTo('head');
                    }
                });
            })
        ).then(function(){
            if (typeof callback=='function') callback();
        });
    },
});

var cssfiles=['/css/normalize.css?jobofferinsidebar', '/css/tricks.css?jobofferinsidebar'];

$.getCss(cssfiles, function(){ console.log('all css loaded'); }); Usage

var cssfiles=['/css/normalize.css', '/css/tricks.css'];

$.getManyCss(cssfiles, function(){
    // do something, e.g.
    console.log('all css loaded');
});

to force refresh the css files add true

$.getManyCss(cssfiles, function(){
    // do something, e.g.
    console.log('all css loaded');
}, true);


回答2:

The answer given by @Popnoodles is not correct because the callback is not executed after all items have been loaded, but rather when the $.each loop is finished. The reason is, that $.each operation does not return a Deferred object (which is expected by $.when).

Here is a corrected example:

$.extend({
    getCss: function(urls, callback, nocache){
        if (typeof nocache=='undefined') nocache=false; // default don't refresh
        $.when.apply($,
            $.map(urls, function(url){
                if (nocache) url += '?_ts=' + new Date().getTime(); // refresh? 
                return $.get(url, function(){                    
                    $('<link>', {rel:'stylesheet', type:'text/css', 'href':url}).appendTo('head');                    
                });
            })
        ).then(function(){
            if (typeof callback=='function') callback();
        });
    }
});


回答3:

Here is how I would load it:

$(document).ready( function() {
    var css = jQuery("<link>");
    css.attr({
      rel:  "stylesheet",
      type: "text/css",
      href: "path/to/file/style.css"
    });
    $("head").append(css);
});


回答4:

You are trying to achieve lazy loading of your resources. There are different plug ins to handle this kind of behavior.

I've can name this two:

  • Jquery Plugins
  • Lazy

Two snippets from the plugins page to show it's use:

Jquery Plugins

$.plugins({ path: '/scripts/', plugins: [        
    { id:'box', js:'box.js', css:'box/styles.css', sel:'a[rel*=box]', ext:'box', fn:'box' },        
]});

jQuery(document).ready(function($){
  $('a').box();
});

Lazy with dependencies:

$.lazy('ui.draggable.js','draggable',{
'css':['modal.css','drag.css'],
'js':['ui.core.js']
});

// And then you use you plugins as you always do
$("#draggable").draggable();


回答5:

The solution can be improved a bit I think... First of all, when you use this lines of code:

...
$.get(url, function(){                    
    $('<link>', {rel:'stylesheet', type:'text/css', 'href':url}).appendTo('head');
});
...

You're really making 2 calls to retrieve the css: first in the $.get itself and a second time when you append the <link> into the head. Removing the $.get will also retrieve the css, but just once:

...
$('<link>', {rel:'stylesheet', type:'text/css', 'href':url}).appendTo('head');
...

But if you need to do more things (eg. loading a script file) while retrieving these css' before calling the callback function I think a better approach for this would be using promises instead of the when...then solution. You could do something like this:

var requests = []; //array which will contain every request we want to do before calling the callback function

$.map(urls, function(url) { //urls is the array of css files we want to load
    var defer = $.Deferred();
    defer.promise();

    //we add the deferred object to the requests array
    requests.push(defer);

    var cssEl = $('<link>', { rel: 'stylesheet', type: 'text/css', 'href': url });
    cssEl.appendTo('head').on("load", function() {
        defer.resolve();
    });
});

var anotherRequest = $.ajax({...}); //eg. a script file
requests.push(anotherRequest);

$.when.apply($, requests).done(function() {
    // callback when all requests are done
});

This way, if some of the css takes some time to load, the callback function won't be executed until all of them are retrieved.



回答6:

If you want to load multiple CSS files, but want them to be loaded one by one, you can use solution below:

var getCss = function (url, callback) {
        $.get(url, function () {
            $('<link>', {rel: 'stylesheet', type: 'text/css', 'href': url}).appendTo('head');
            if (typeof callback == 'function') callback();
        });
    };

Define function above, then define array containing you wanted css files

var cssfiles = ['css/pic1.css', 'css/pic2.css',
    'css/pic3.css', 'css/pic4.css', 'css/pic5.css',
    'css/pic6.css', 'css/pic7.css', 'css/pic8.css',
    'css/pic9.css'];

Then define callback function that will call each css files in the array

var callback = function (index) {
    getCss(cssfiles[index], function () {
        if (index + 1 < cssfiles.length) {
            callback(index + 1);
        }
    });
};

then start function with first css file by giving its index

callback(0);


回答7:

If you want it to by dynamic (read: on demand) you can modify Whit's response to loadCssFile() and call that with the file you want to load.

function loadCssFile(pathToFile) {
    var css = jQuery("<link>");
    css.attr({
      rel:  "stylesheet",
      type: "text/css",
      href: pathToFile
    });
    $("head").append(css);
}


回答8:

THIS DOES NOT REQUIRE THE CSS FILE(S) TO BE LOADED MORE THAN ONCE

Working off from the other established answers, I managed to come up with this, which seems to work similar to jQuery.getScript():

(function($){

    $.extend({
        getCss: function(url, success) {

            if ($("head").children("style[data-url='"+url+"']").length) {
                console.warn("CSS file already loaded: "+url);
            }

            var deferred = $.Deferred(function(defer){
                $.ajax({
                    url: url
                    ,context: {defer:defer,url:url}
                    ,dataType: 'text'
                    ,cache: (function(){
                        var cache = $.ajaxSetup().cache;
                        if (cache === undefined) {cache = false;}
                        return cache;
                    })()
                })
                .then(
                    function(css, textStatus, jqXHR){
                        css = "\n\n/* CSS dynamically loaded by jQuery */\n\n"+css;
                        $('<style type="text/css" data-url="'+this.url+'">'+css+'</style>').appendTo("head");
                        this.defer.resolve(css, textStatus, jqXHR);
                    }
                    ,function(jqXHR, textStatus, errorThrown){
                        this.defer.reject(jqXHR, textStatus, errorThrown);
                    }
                );
            });
            if ($.isFunction(success)) {deferred.done(success);}
            return deferred;
        }
    });

})(jQuery);

It doesn't load the requested file more than once, which DOES require that the CSS content is statically stored in the head. But, it makes sense considering that loading/evaluating JavaScript is different than how styling is accessible to the browser for rendering.

$.getCss('path/to/css/file.css')
.done(function(){
    console.log("Worked");
 })
.fail(function(){
    console.log("Failed");
});

So far I've tested it with Chrome, IE, and Firefox. All seem to work fine.



标签: jquery css load