jQuery:按需加载CSS + 加载完成后回调

31

我想按需加载CSS文件(例如通过运行XML HTTP请求返回要加载的CSS文件,例如style1.css, style2.css等),那么在jQuery中(或者插件中)有办法实现吗?

  • 批量加载多个文件并将所有CSS文件添加到DOM中
  • 当加载完成时:触发回调函数(例如弹出“所有样式表已完成加载!”);

思路是:通过xmlhttp加载html,加载和添加必需的CSS文件,然后等待所有内容都加载完毕后,再显示html

有什么好的想法吗?

谢谢!

8个回答

24
如何加载多个CSS文件,并在请求时带有回调
注意:没有跨域权限,$.get只能加载本地文件。 工作演示 请注意,在加载完成之后但CSS应用于之前,文本“all css loaded”会出现。也许需要另一种解决方法来克服这个问题。
$.extend({
    getManyCss: 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();
        });
    },
});

使用方法

var cssfiles=['https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css', 'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/cerulean/bootstrap.min.css'];

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

要强制刷新 CSS 文件,请添加 true。

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

3
对于其他人的疑问:该解决方案不会在CSS加载完成时触发回调函数(如问题所要求)。但由于资源是事先获取的,因此在$ .get()解析后触发的回调将立即触发,因为资源已经在浏览器缓存中。确实是个好的解决方案,应该被接受为答案。 - Dynalon
资源加载后,它们会立即附加到页面上。当所有资源都加载完成后,回调函数将被触发。我将测试在链接添加到头部之前回调函数是否执行。 - Popnoodles
@Dyna 你说得对,它会在所有 $.get() 请求都完成后触发,但在生效之前。我不喜欢这个解决方案,但我可能会尝试使用 setInterval 来查看最后一个 <link> 何时被添加。一个简单的修复方法是 if (typeof callback=='function') setTimeout(callback, 500); 延迟为0适用于某些问题,但并不足够。一定有更简洁的方法... - Popnoodles
1
请注意,这并不保证样式表按照给定的顺序被附加。要执行此操作,您需要循环遍历样式表,并在“when”的“then”部分内将它们作为<link>附加到head中。 - rolebi
我对这个答案进行了扩展。我刚刚提供的答案不会调用资源两次。 - Erutan409
显示剩余7条评论

9
@Popnoodles 给出的答案是不正确的,因为回调函数并不是在所有项目加载完毕后执行的,而是在 $.each 循环结束后执行的。原因是 $.each 操作不返回 Deferred 对象($.when 期望获得这种对象)。
下面是一个修改过的示例:
$.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();
        });
    }
});

没错,它可以工作。但是你需要在function(i, url)中交换iurl的位置才能真正地使其工作。 - spaceman
是的,这个完美地运作了。就像 @Alberto 所说的那样,$.each 不会返回一个 Deferred 对象,而这是 $.when 所必需的。因此,当使用 $.map 时,它非常顺畅。 - Balasubramani M

8
这是我如何加载它的方式:
$(document).ready( function() {
    var css = jQuery("<link>");
    css.attr({
      rel:  "stylesheet",
      type: "text/css",
      href: "path/to/file/style.css"
    });
    $("head").append(css);
});

5
没问题,$(document).ready事件会在jQuery加载完之后触发。 - Whit
5
这只回答了提问者问题的一半:另一半是“在加载完成后触发回调函数”。不幸的是,将.load(function(){})添加到创建的链接中不起作用。 - Tom Auger
6
答案不完整,只解决了问题的一半,也就是简单的部分。难点在于CSS资源加载后的回调处理。请将此补充完整。 - Dynalon
1
@Dyna 这并不难。 - Popnoodles
@Popnoodles,我对你解决那个问题的方案非常感兴趣,能分享一下吗? - Dynalon
显示剩余3条评论

2

我认为这个解决方案可以做些改进... 首先,在使用以下代码行时:

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

你实际上进行了两次调用来获取css: 第一次是在$.get中,第二次是将<link>添加到head时。 如果删除$.get,也会获取css,但只需要一次:

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

但是,如果在调用回调函数之前需要执行更多操作(例如加载脚本文件),我认为更好的方法是使用promises而不是when...then解决方案。你可以这样做:

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
});

这样,如果某些CSS需要一些时间才能加载,回调函数将不会被执行,直到它们都被检索出来。

1

您正在尝试实现资源的延迟加载。

有不同的插件可以处理这种行为。

我可以提到这两个:

来自插件页面的两个片段以展示它的使用:

Jquery 插件

$.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('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();

1

这不需要加载CSS文件超过一次

在其他已有答案的基础上,我设法想出了这个解决方案,它似乎类似于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);

它不会多次加载请求的文件,这要求CSS内容在头部静态存储。但是,考虑到加载/评估JavaScript与浏览器呈现样式的可访问性不同,这是有道理的。

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

到目前为止,我已经在Chrome、IE和Firefox上进行了测试。所有浏览器似乎都能正常工作。


0
如果你想让它变得动态(即按需加载),你可以修改Whit的响应,使用loadCssFile()并调用要加载的文件。
function loadCssFile(pathToFile) {
    var css = jQuery("<link>");
    css.attr({
      rel:  "stylesheet",
      type: "text/css",
      href: pathToFile
    });
    $("head").append(css);
}

0
如果您想加载多个CSS文件,但希望它们逐个加载,可以使用以下解决方案:
var getCss = function (url, callback) {
        $.get(url, function () {
            $('<link>', {rel: 'stylesheet', type: 'text/css', 'href': url}).appendTo('head');
            if (typeof callback == 'function') callback();
        });
    };

先定义函数,然后定义包含所需 CSS 文件的数组

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'];

然后定义回调函数,该函数将调用数组中的每个CSS文件。
var callback = function (index) {
    getCss(cssfiles[index], function () {
        if (index + 1 < cssfiles.length) {
            callback(index + 1);
        }
    });
};

然后通过给出第一个CSS文件的索引来启动函数

callback(0);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接