web-dev-qa-db-fra.com

élément de lien en charge

Est-il possible d'écouter l'événement onload pour un élément <link>?

F.ex:

var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css';

link.onload = link.onreadystatechange = function(e) {
    console.log(e);
};

Cela fonctionne pour les éléments <script>, mais pas pour <link>. Existe-t-il un autre moyen? J'ai juste besoin de savoir quand les styles de la feuille de style externe ont été appliqués au DOM.

Mettre à jour:

Serait-ce une idée d'injecter un <iframe> caché, d'ajouter le <link> à la tête et d'écouter l'événement window.onload dans l'iframe? Il devrait se déclencher lorsque le fichier css est chargé, mais cela ne garantit pas nécessairement qu'il est chargé dans la fenêtre du haut ...

38
David Hellsing

C'est un peu un bidouillage, mais si vous pouvez modifier le CSS, vous pouvez ajouter un style spécial (sans effet visible) que vous pouvez écouter en utilisant la technique décrite dans ce post: http: //www.west-wind .com/weblog/posts/478985.aspx

Vous auriez besoin d’un élément dans la page ayant une classe ou un identifiant que le CSS affectera. Lorsque votre code détecte que son style a changé, le CSS est chargé.

Un bidouillage, comme je l'ai dit :)

7
Peter Jaric

Aujourd'hui, tous les navigateurs modernes prennent en charge l'événement onload sur les balises link. Donc, je garderais des hacks, comme créer un élément img et régler l’erreur:

if !('onload' in document.createElement('link')) {
  imgTag = document.createElement(img);
  imgTag.onerror = function() {};
  imgTag.src = ...;
} 

Cela devrait fournir une solution de contournement pour les versions FF-8 et antérieures et anciennes de Safari & Chrome.

mise à jour mineure:

Comme Michael l'a souligné, il existe certaines exceptions de navigateur pour lesquelles nous voulons toujours appliquer le hack. Dans Coffeescript:

isSafari5: ->
  !!navigator.userAgent.match(' Safari/') &&
      !navigator.userAgent.match(' Chrom') &&
      !!navigator.userAgent.match(' Version/5.')

# Webkit: 535.23 and above supports onload on link tags.
isWebkitNoOnloadSupport: ->
  [supportedMajor, supportedMinor] = [535, 23]
  if (match = navigator.userAgent.match(/\ AppleWebKit\/(\d+)\.(\d+)/))
    match.shift()
    [major, minor] = [+match[0], +match[1]]
    major < supportedMajor || major == supportedMajor && minor < supportedMinor
16
mlangenberg

La façon dont je l'ai fait sur Chrome (non testé sur d'autres navigateurs) est de charger le CSS à l'aide d'un objet Image et de capturer son événement onerror. Le fait est que le navigateur ne sait pas si cette ressource est une image ou non, aussi essaiera-t-elle de la récupérer. Cependant, comme il ne s'agit pas d'une image réelle, cela déclenchera des gestionnaires onerror

var css = new Image();
css.onerror = function() {
    // method body
}
// Set the url of the CSS. In link case, link.href
// This will make the browser try to fetch the resource.
css.src = url_of_the_css;

Notez que si la ressource a déjà été extraite, cette demande d'extraction atteindra le cache.

3
bellpeace

Par exemple. Le navigateur Android ne prend pas en charge les événements "onload"/"onreadystatechange" pour l'élément: http://pieisgood.org/test/script-link-events/
Mais il retourne:

"onload" in link === true

Ma solution consiste donc à détecter le navigateur Android à partir de userAgent, puis à attendre une règle CSS spéciale dans votre feuille de style (par exemple, réinitialiser les marges du "corps").
S'il ne s'agit pas d'un navigateur Android et qu'il prend en charge l'événement "onload", nous l'utilisons:

var userAgent = navigator.userAgent,
    iChromeBrowser = /CriOS|Chrome/.test(userAgent),
    isAndroidBrowser = /Mozilla\/5.0/.test(userAgent) && /Android/.test(userAgent) && /AppleWebKit/.test(userAgent) && !iChromeBrowser; 

addCssLink('PATH/NAME.css', function(){
    console.log('css is loaded');
});

function addCssLink(href, onload) {
    var css = document.createElement("link");
    css.setAttribute("rel", "stylesheet");
    css.setAttribute("type", "text/css");
    css.setAttribute("href", href);
    document.head.appendChild(css);
    if (onload) {
        if (isAndroidBrowser || !("onload" in css)) {
            waitForCss({
                success: onload
            });
        } else {
            css.onload = onload;
        }
    }
}

// We will check for css reset for "body" element- if success-> than css is loaded
function waitForCss(params) {
    var maxWaitTime = 1000,
        stepTime = 50,
        alreadyWaitedTime = 0;

    function NeXTSTEP() {
        var startTime = +new Date(),
            endTime;

        setTimeout(function () {
            endTime = +new Date();
            alreadyWaitedTime += (endTime - startTime);
            if (alreadyWaitedTime >= maxWaitTime) {
                params.fail && params.fail();
            } else {
                // check for style- if no- revoke timer
                if (window.getComputedStyle(document.body).marginTop === '0px') {
                    params.success();
                } else {
                    NeXTSTEP();
                }
            }
        }, stepTime);
    }

    NeXTSTEP();
}

Démo: http://codepen.io/malyw/pen/AuCtH

2
Serg Hospodarets

Depuis que tu n'as pas aimé mon bidouillage :) J'ai regardé autour de moi d'une autre manière et ai trouvé un par brothercake .

En gros, il est suggéré de faire en sorte que le CSS utilise AJAX pour que le navigateur le mette en cache, puis traite le chargement du lien comme instantané, car le CSS est mis en cache. Cela ne fonctionnera probablement pas à chaque fois (du fait que certains navigateurs peuvent avoir le cache désactivé, par exemple), mais presque toujours. 

1
Peter Jaric

Une autre façon de procéder consiste à vérifier le nombre de feuilles de style chargées. Par exemple:

Avec "css_filename" l'URL ou le nom du fichier css et "callback" une fonction de rappel lorsque le css est chargé:

var style_sheets_count=document.styleSheets.length;
var css = document.createElement('link');
css.setAttribute('rel', 'stylesheet');
css.setAttribute('type', 'text/css');
css.setAttribute('href', css_filename);
document.getElementsByTagName('head').item(0).appendChild(css);
include_javascript_wait_for_css(style_sheets_count, callback, new Date().getTime());

function include_javascript_wait_for_css(style_sheets_count, callback, starttime)
/* Wait some time for a style sheet to load.  If the time expires or we succeed
 *  in loading it, call a callback function.
 * Enter: style_sheet_count: the original number of style sheets in the
 *                           document.  If this changes, we think we finished
 *                           loading the style sheet.
 *        callback: a function to call when we finish loading.
 *        starttime: Epoch when we started.  Used for a timeout. 12/7/11-DWM */
{  
   var timeout = 10000; // 10 seconds
   if (document.styleSheets.length!=style_sheets_count || (new Date().getTime())-starttime>timeout)
      callback();
   else
      window.setTimeout(function(){include_javascript_wait_for_css(style_sheets_count, callback, starttime)}, 50);
}
1
garlon4

Cette astuce est empruntée au plugin jQuery xLazyLoader :

var count = 0;

(function(){
  try {
    link.sheet.cssRules;
  } catch (e) {
    if(count++ < 100)
      cssTimeout = setTimeout(arguments.callee, 20);
    else
      console.log('load failed (FF)');
    return;
  };
  if(link.sheet.cssRules && link.sheet.cssRules.length == 0) // fail in chrome?
    console.log('load failed (Webkit)');
  else
    console.log('loaded');
})();

Testé et fonctionnant localement sous FF (3.6.3) et Chrome (linux - 6.0.408.1 dev)

Démo ici (notez que cela ne fonctionnera pas pour le chargement de CSS sur plusieurs sites, comme dans la démonstration, sous FF)

0
sje397
// this work in IE 10, 11 and Safari/Chrome/Firefox/Edge
// if you want to use Promise in an non-es6 browser, add an ES6 poly-fill (or rewrite to use a callback)

let fetchStyle = function(url) {
  return new Promise((resolve, reject) => {
    let link = document.createElement('link');
    link.type = 'text/css';
    link.rel = 'stylesheet';
    link.onload = resolve;
    link.href = url;

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

Vous avez besoin d'un élément spécifique dont vous connaissez le style ou, si vous contrôlez le fichier CSS, vous pouvez insérer un élément factice à cette fin. Ce code exécutera exactement votre rappel lorsque le contenu du fichier css est appliqué au DOM. 

// dummy element in the html
<div id="cssloaded"></div>

// dummy element in the css
#cssloaded { height:1px; }

// event handler function
function cssOnload(id, callback) {
  setTimeout(function listener(){
    var el = document.getElementById(id),
        comp = el.currentStyle || getComputedStyle(el, null);
    if ( comp.height === "1px" )
      callback();
    else
      setTimeout(listener, 50);
  }, 50)
}

// attach an onload handler
cssOnload("cssloaded", function(){ 
  alert("ok"); 
});

Si vous utilisez ce code au bas du document, vous pouvez déplacer les variables el et comp en dehors du minuteur pour obtenir l’élément une fois. Mais si vous souhaitez attacher le gestionnaire quelque part dans le document (comme la tête), vous devez laisser le code tel quel.

Remarque: testé sur FF 3+, IE 5.5+, Chrome

0
gblazex

Le plug-in xLazyLoader échoue car les propriétés de cssRules sont masquées pour les feuilles de style appartenant à d'autres domaines (rompt la même stratégie d'origine). Donc, ce que vous devez faire est de comparer les propriétés ownerNode et owningElements.

Voici une explication détaillée de ce que vous devez faire: http://yearofmoo.com/2011/03/cross-browser-stylesheet-preloading/

0
matsko