Jak wrócić do lokalnego arkusza stylów (nie skryptu), jeśli CDN zawiedzie


108

Łączę się z arkuszem stylów jQuery Mobile w sieci CDN i chciałbym wrócić do mojej lokalnej wersji arkusza stylów, jeśli CDN zawiedzie. W przypadku skryptów rozwiązanie jest dobrze znane:

<!-- 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>

Chciałbym zrobić coś podobnego dla arkusza stylów:

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

Nie jestem pewien, czy można osiągnąć podobne podejście, ponieważ nie jestem pewien, czy przeglądarka blokuje się w ten sam sposób podczas łączenia skryptu, jak to robi podczas ładowania skryptu (być może można załadować arkusz stylów w tagu skryptu, a następnie wstrzyknąć go na stronę)?

Więc moje pytanie brzmi: jak upewnić się, że arkusz stylów jest ładowany lokalnie, jeśli CDN zawiedzie?


2
Chciałbym wiedzieć, czy to również jest możliwe ... Gdybym naprawdę martwił się, że CDN nie działa, użyłbym tylko lokalnego hostingu.
Fosco

2
@Stefan Kendall, myślę, że słusznym stwierdzeniem jest to, że jego witryna prawdopodobnie upadnie niż CDN
Shawn Mclean,

Odpowiedzi:


63

Nie testowane na różnych przeglądarkach, ale myślę, że to zadziała. Będzie to jednak musiało nastąpić po załadowaniu jquery, albo będziesz musiał przepisać go w zwykłym języku 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>

dobre rozwiązanie, jednym z problemów, których nie rozwiązuje, jest to, że CDN jest zbyt wolne, aby się załadować ... może jakiś limit czasu?
GeorgeU,

2
W przypadku code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css otrzymuję rules = null, mimo że został poprawnie załadowany. Używam Chrome 26 i myślę, że to dlatego, że skrypt jest między domenami?
simplfuzz

8
To rozwiązanie tak naprawdę nie działa dla wszystkich CDN / Arkuszy stylów, na przykład CSSStyleSheetobiekty js pochodzące z bootstrapcdn.com mają puste rulesi cssRulespola w mojej przeglądarce (Chrome 31). UPD : w rzeczywistości może to być problem między domenami, plik css w odpowiedzi również nie działa dla mnie.
Maksim Vi.

3
Jest to również dobre rozwiązanie przy użyciu onerrorzdarzenia. stackoverflow.com/questions/30546795/…
Programator chemiczny

2
Czy to działa w przypadku CSS załadowanego z innej domeny? Nie, nie możesz wyliczyć .rules/ .cssRulesdla zewnętrznych arkuszy stylów. jsfiddle.net/E6yYN/13
Salman A

29

Chyba chodzi o to, aby wykryć, czy arkusz stylów jest załadowany, czy nie. Jedno możliwe podejście jest następujące:

1) Dodaj specjalną regułę na końcu pliku CSS, na przykład:

#foo { display: none !important; }

2) Dodaj odpowiedni element div do kodu HTML:

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

3) Gdy dokument jest gotowy, sprawdź, czy #foojest widoczny, czy nie. Jeśli arkusz stylów został załadowany, nie będzie widoczny.

Demo tutaj - ładuje motyw płynności jquery-ui; żadna reguła nie jest dodawana do arkusza stylów.


42
+1 za sprytne rozwiązanie. Jedynym problemem jest to, że normalnie nie można przejść i dodać linii na końcu arkusza stylów, który jest hostowany na czyimś CDN
Jannie Theunissen

2
Niestety nie możemy wpisać naszych własnych klas do plików CDN. Może spróbujemy wykorzystać ten, który już istnieje.
Ahamed

1
Bardzo mi się podoba, dziękuję. Jest naprawdę potężny. Oczywiście nie mogę manipulować arkuszem stylów CDN, ale wiem, jakie klasy są używane, więc poprawiłem kod, aby pokazać, czy są widoczne - bardzo sprytne :)
Nosnibor

3
Uwaga: tak naprawdę nie musisz dodawać nowej reguły do ​​zewnętrznego CSS. Po prostu użyj istniejącej reguły, której zachowanie jest znane. W moim demo używam klasy ui-helper-hidden, która ma ukryć element, a następnie sprawdzam, czy element zostanie ukryty podczas ładowania strony.
Salman A

28

Zakładając, że używasz tego samego CDN dla css i jQuery, dlaczego nie zrobić po prostu jednego testu i złapać wszystko?

<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
Czy mogę zapytać, jaki jest problem z początkowym użyciem ciągów znaków bez zmiany znaczenia, np. document.write("<script type='text/javascript' src='path/to/file.js'>")?
Jack Tuck

2
@JackTuck: Parser nie może odróżnić <script>wewnątrz ciągu JS od tego znalezionego na zewnątrz. Jest to często powód, dla którego widzisz również <\/script>podczas pisania tagów dla awaryjnych CDN.
Brad Christie

6
-1 why not just do one test and catch it all?- Ponieważ istnieje milion powodów, dla których jeden może zawieść, a innym się udać.
Flimzy

7

ten artykuł sugeruje kilka rozwiązań dla bootstrap css http://eddmann.com/posts/providing-local-js-and-css-resources-for-cdn-fallbacks/

alternatywnie działa to w przypadku 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>

Dla tych, którzy chcą tego używać z Font Awesome 5, będziesz chciał zmienić „FontAwesome” (w klauzuli if) na „Font Awesome 5 Free” (jeśli używasz darmowych czcionek). W przeciwnym razie powinno działać dobrze.
Jason Clark

4

Państwo może być w stanie przetestować na istnienie w arkuszu stylów 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

Sprawdź, czy nie ma czegoś konkretnego dla używanego pliku CSS.


4

Można onerrordo tego użyć :

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

Ma this.onerror=null;to na celu uniknięcie niekończących się pętli w przypadku, gdy samo rozwiązanie awaryjne nie jest dostępne. Ale może być również używany do wielu awaryjnych.

Jednak obecnie działa to tylko w przeglądarkach Firefox i Chrome.


3

Oto rozszerzenie odpowiedzi Katy Lavallee. Wszystko zawinęłam w samowykonującą się składnię funkcji, aby zapobiec kolizji zmiennych. Skrypt nie jest też specyficzny dla pojedynczego linku. IE, teraz każde łącze do arkusza stylów z atrybutem adresu URL „data-fallback” zostanie automatycznie przeanalizowane. Nie musisz na stałe kodować adresów URL w tym skrypcie, jak wcześniej. Zauważ, że powinno to być uruchamiane na końcu <head>elementu, a nie na końcu <body>elementu, w przeciwnym razie może to spowodować 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
Podoba mi się hermetyzacja, ale generalnie nie możesz sprawdzić arkusza Sheet.rules pod kątem arkusza stylów międzydomenowego. Nadal możesz skorzystać z tej ogólnej idei, ale musisz zrobić inne sprawdzenie.
John Vinopal,

z document.styleSheets[i].ownerNode.datasettobą możesz uzyskać dostęp do <link data-* />atrybutów
Alwin Kesler,

2

Czy na pewno chcesz skorzystać z tej trasy javascript, aby załadować CSS na wypadek awarii CDN?

Nie przemyślałem wszystkich implikacji dotyczących wydajności, ale stracisz kontrolę nad ładowaniem CSS i ogólnie biorąc pod uwagę wydajność ładowania strony, CSS jest pierwszą rzeczą, którą chcesz pobrać po HTML.

Dlaczego nie załatwić tego na poziomie infrastruktury - zmapuj własną nazwę domeny na CDN, nadaj jej krótki TTL, monitoruj pliki w CDN (np. Za pomocą Watchmouse lub czegoś innego), jeśli CDN zawiedzie, zmień DNS na kopię zapasową.

Inne opcje, które mogą pomóc, to „pamięć podręczna na zawsze” w statycznej zawartości, ale nie ma gwarancji, że przeglądarka będzie je oczywiście zachowywać lub używać pamięci podręcznej aplikacji.

W rzeczywistości, jak ktoś powiedział na górze, jeśli twój CDN jest zawodny, zdobądź nowy

Andy


7
CDN może być niezawodny, ale nie połączenie internetowe środowiska programistycznego;)
lodołamacz

1

Spójrz na te funkcje:

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

A oto waniliowa wersja 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();
}

Teraz rzeczywista funkcja:

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

Po prostu upewnij się, że w głowie wywoływana jest AddLocalCss.

Możesz również rozważyć użycie jednego z następujących sposobów wyjaśnionych w tej odpowiedzi :

Załaduj za pomocą AJAX

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

Załaduj za pomocą utworzonego dynamicznie

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

lub

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

Tak, przynajmniej w nowoczesnej przeglądarce nie jestem pewien co do IE6.

Czy jest sposób, aby po prostu sprawdzić, zamiast pobierać całość?
Shawn Mclean

Jedynym możliwym powodem wykonania żądania PO jest uniknięcie nadmiernego ruchu w sieci. Powoduje to nadmierny ruch w sieci.
Stefan Kendall,

@stefan Kendall: nie, to nawet nie jest możliwy powód, dla którego chce się upewnić, że pliki zostaną załadowane.

Gdyby to był jedyny problem, po prostu nie używałbyś CDN. Testowanie samego nagłówka jest lepsze, ale jestem prawie pewien, że większość CDN i twoja przeglądarka nie zezwoli na XSS.
Stefan Kendall,

-1

Prawdopodobnie użyłbym czegoś takiego jak 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');
    }
  }
}]);

Zaczerpnięte z pliku readme.


10
@BenSchwarz, to nie znaczy, że możesz wkleić jakiś nieistotny kod, który w żaden sposób nie odpowiada na zadane pytanie.
AlicanC

-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>
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.