如果CDN失败,如何退回到本地样式表(不是脚本)


108

我正在链接到CDN上的jQuery Mobile样式表,如果CDN失败,我想回退到样式表的本地版本。对于脚本,解决方案是众所周知的:

<!-- Load jQuery and jQuery mobile with fall back to local server -->
<script src="http://code.jquery.com/jquery-1.6.3.min.js"></script>
<script type="text/javascript">
  if (typeof jQuery == 'undefined') {
    document.write(unescape("%3Cscript src='jquery-1.6.3.min.js'%3E"));
  }
</script>

我想为样式表做类似的事情:

<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0b3/jquery.mobile-1.0b3.min.css" />

我不确定是否可以实现类似的方法,因为我不确定浏览器在链接脚本时是否以与加载脚本时相同的方式进行阻止(也许可以在script标签中加载样式表,然后将其注入页面)?

所以我的问题是:如果CDN失败,如何确保样式表在本地加载?


2
我想知道是否也可以...如果我真的对CDN崩溃感到不安,我将使用本地托管。
Fosco 2011年

2
@Stefan Kendall,我认为正确的说法是,他的网站比CDN更有可能崩溃
Shawn Mclean

Answers:


63

未经跨浏览器测试,但我认为这会起作用。不过必须在加载jquery之后,否则必须用纯Javascript重写它。

<script type="text/javascript">
$.each(document.styleSheets, function(i,sheet){
  if(sheet.href=='http://code.jquery.com/mobile/1.0b3/jquery.mobile-1.0b3.min.css') {
    var rules = sheet.rules ? sheet.rules : sheet.cssRules;
    if (rules.length == 0) {
      $('<link rel="stylesheet" type="text/css" href="path/to/local/jquery.mobile-1.0b3.min.css" />').appendTo('head');
    }
 }
})
</script>

好的解决方案,它没有解决的一个问题是CDN加载速度太慢...也许某种超时?
GeorgeU 2011年

2
对于code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css,即使正确加载,我也会得到rules = null。我使用的是Chrome 26,我认为这是因为脚本是跨域的吗?
simplfuzz

8
该解决方案并不真正适用于所有CDN /样式表,例如CSSStyleSheet,来自bootstrapcdn.com的js对象在我的浏览器(Chrome 31)中都具有空白rulescssRules字段。UPD:这实际上可能是跨域问题,答案中的css文件对我也不起作用。
马克西姆六世。

3
这也是使用onerror事件的好方法。stackoverflow.com/questions/30546795/...
化学程序员

2
它适用于从另一个域加载的CSS吗?不,您不能为外部样式表枚举.rules/ .cssRulesjsfiddle.net/E6yYN/13
Salman A

29

我想问题是要检测是否加载了样式表。一种可能的方法如下:

1)在CSS文件的末尾添加一个特殊规则,例如:

#foo { display: none !important; }

2)在HTML中添加相应的div:

<div id="foo"></div>

3)准备好文件后,检查是否#foo可见。如果加载了样式表,则它将不可见。

此处演示 -加载jquery-ui平滑度主题;没有规则添加到样式表。


42
+1是一个聪明的解决方案。唯一的问题是,通常不能到在某人的CDN上托管的样式表的末尾添加一行
Jannie Theunissen

2
不幸的是,我们不能在CDN文件中输入自己的类。也许我们可以尝试利用已经存在的一种。
艾哈迈德(Ahamed)2014年

1
我很喜欢这个,谢谢。确实非常强大。显然,我无法操纵CDN样式表,但我知道正在使用哪些类,因此我修改了代码以显示它们是否可见-确实非常聪明:)
Nosnibor 2015年

3
注意:您实际上不必向外部CSS添加新规则。只需使用已知行为的现有规则即可。在我的演示中,我使用了ui-helper-hidden类,该类应该隐藏该元素,然后检查该元素是否在页面加载时被隐藏。
Salman A

28

假设您为css和jQuery使用相同的CDN,为什么不只做一个测试并全部捕获呢?

<link href="//ajax.googleapis.com/ajax/libs/jqueryui/1/themes/start/jquery-ui.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>
<script type="text/javascript">
    if (typeof jQuery == 'undefined') {
        document.write(unescape('%3Clink rel="stylesheet" type="text/css" href="../../Content/jquery-ui-1.8.16.custom.css" /%3E'));
        document.write(unescape('%3Cscript type="text/javascript" src="/jQuery/jquery-1.6.4.min.js" %3E%3C/script%3E'));
        document.write(unescape('%3Cscript type="text/javascript" src="/jQuery/jquery-ui-1.8.16.custom.min.js" %3E%3C/script%3E'));
    }
</script>

1
请问最初使用未转义的字符串有什么问题document.write("<script type='text/javascript' src='path/to/file.js'>")
杰克·塔克

2
@JackTuck:解析器无法区分<script>JS字符串内部和外部。这就是通常<\/script>在写出CDN后备标签时也会看到的原因。
Brad Christie 2015年

6
-1- why not just do one test and catch it all?因为有百万种原因可能使一个失败,而另一个可能成功。
Flimzy

7

本文提出了一些针对引导CSS的解决方案,网址为 http://eddmann.com/posts/providing-local-js-and-css-resources-for-cdn-fallbacks/

或者这适用于fontawesome

<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<script>
    (function($){
        var $span = $('<span class="fa" style="display:none"></span>').appendTo('body');
        if ($span.css('fontFamily') !== 'FontAwesome' ) {
            // Fallback Link
            $('head').append('<link href="https://stackoverflow.com/css/font-awesome.min.css" rel="stylesheet">');
        }
        $span.remove();
    })(jQuery);
</script>

对于那些希望与Font Awesome 5一起使用的用户,您需要将“ FontAwesome”(在if子句中)更改为“ Font Awesome 5 Free”(如果您使用的是免费字体)。否则,它应该可以正常工作。
杰森·克拉克

4

也许可以测试中样式表的存在document.styleSheets

var rules = [];
if (document.styleSheets[1].cssRules)
    rules = document.styleSheets[i].cssRules
else if (document.styleSheets[i].rules)
    rule= document.styleSheets[i].rules

测试特定于您正在使用的CSS文件的内容。


4

一个可以onerror用于:

<link rel="stylesheet" href="cdn.css" onerror="this.onerror=null;this.href='local.css';" />

这样做this.onerror=null;是为了避免无休止的循环,以防自身无法使用后备广告。但是它也可以用来有多个后备。

但是,这目前仅在Firefox和Chrome中有效。


3

这是对凯蒂·拉瓦利的回答的扩展。我将所有内容包装在自执行函数语法中,以防止发生变量冲突。我还使脚本非特定于单个链接。IE,现在任何具有“ data-fallback” URL属性的样式表链接都将被自动解析。您不必像以前一样将URL硬编码到此脚本中。请注意,这应该在<head>元素的末尾而不是元素的末尾运行<body>,否则可能会导致FOUC

http://jsfiddle.net/skibulk/jnfgyrLt/

<link rel="stylesheet" type="text/css" href="broken-link.css" data-fallback="broken-link2.css">

(function($){
    var links = {};

    $( "link[data-fallback]" ).each( function( index, link ) {
        links[link.href] = link;
    });

    $.each( document.styleSheets, function(index, sheet) {
        if(links[sheet.href]) {
            var rules = sheet.rules ? sheet.rules : sheet.cssRules;
            if (rules.length == 0) {
                link = $(links[sheet.href]);
                link.attr( 'href', link.attr("data-fallback") );
            }
        }
    });
})(jQuery);

1
我喜欢这种封装,但是总的来说,您不能检查sheet.rules的跨域样式表。您仍然可以使用此总体思路,但需要进行其他检查。
John Vinopal

document.styleSheets[i].ownerNode.dataset您可以访问<link data-* />属性
阿尔文凯斯勒

2

如果CDN失败,您是否真的要拒绝使用此JavaScript路由来加载CSS?

我还没有考虑过所有的性能影响,但是您将失去对何时加载CSS的控制,并且一般而言,对于页面加载性能,CSS是您要在HTML之后下载的第一件事。

为什么不在基础架构级别处理此问题-将您自己的域名映射到CDN,给它一个短的TTL,监视CDN上的文件(例如使用Watchmouse或其他方法),如果CDN失败,则将DNS更改为备份站点。

其他可能有用的选项是在静态内容上“永久缓存”,但不能保证浏览器会保留它们不变或使用应用程序缓存。

实际上,就像某人在顶部说的那样,如果您的CDN不可靠,请换一个新的

安迪


7
CDN可以可靠,但不能与开发环境Internet连接;)
破冰船者

1

查看以下功能:

$.ajax({
    url:'CSS URL HERE',
    type:'HEAD',
    error: function()
    {
        AddLocalCss();
    },
    success: function()
    {
        //file exists
    }
});

这是原始的JavaScript版本:

function UrlExists(url)
{
    var http = new XMLHttpRequest();
    http.open('HEAD', url, false);
    http.send();
    return http.status!=404;
}
if (!UrlExists('CSS URL HERE') {
AddLocalCss();
}

现在的实际功能是:

function AddLocalCss(){
document.write('<link rel="stylesheet" type="text/css" href=" LOCAL CSS URL HERE">')
}

只要确保在头中调用了AddLocalCss。

您也可以考虑使用此答案中说明的以下方式之一

使用AJAX加载

$.get(myStylesLocation, function(css)
{
   $('<style type="text/css"></style>')
      .html(css)
      .appendTo("head");
});

使用动态创建的负载

$('<link rel="stylesheet" type="text/css" href="'+myStylesLocation+'" >')
   .appendTo("head");
Load using dynamically-created <style>

$('<style type="text/css"></style>')
    .html('@import url("' + myStylesLocation + '")')
    .appendTo("head");

要么

$('<style type="text/css">@import url("' + myStylesLocation + '")</style>')
    .appendTo("head");

是的,至少在现代浏览器中,我不确定IE6。

有没有一种方法可以检查而不是下载整个程序?
肖恩·麦克林

进行OP请求的唯一可能原因是避免过多的网络流量。这会产生过多的网络流量。
Stefan Kendall

@stefan Kendall:不,这甚至不是他想要确保文件被加载的可能原因。

如果这是唯一的问题,您将不会使用CDN。仅测试标题会更好,但是我敢肯定大多数CDN和您的浏览器都不会允许XSS。
Stefan Kendall

-1

我可能会使用像yepnope.js这样的东西

yepnope([{
  load: 'http:/­/ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js',
  complete: function () {
    if (!window.jQuery) {
      yepnope('local/jquery.min.js');
    }
  }
}]);

摘自自述文件。


10
@BenSchwarz,这并不意味着您可以粘贴一些不相关的代码,这些代码绝不能回答所问的问题。
AlicanC 2013年

-8
//(load your cdn lib here first)

<script>window.jQuery || document.write("<script src='//me.com/path/jquery-1.x.min.js'>\x3C/script>")</script>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.