Webkit – feuille de style créée dynamiquement – quand est-ce qu'elle est vraiment chargée?

J'ai un certain code (ce n'est pas le mien, mais la bibliothèque SlickGrid ) qui crée un élément <style> , l'insère dans le DOM, puis essaie immédiatement de trouver la nouvelle feuille de style dans la collection document.styleSheets.

Dans WebKit, cela échoue parfois. Je n'ai vraiment aucune idée de ce que sont les circonstances, mais ce n'est rien qui soit constamment reproductible. J'ai pensé que je pouvais contourner cela en changeant le code afin que la vérification de l'objet StyleSheet n'arrive pas jusqu'à l'événement de load sur l'élément de style, de la manière suivante:

  $style = $("<style type='text/css' rel='stylesheet' />").appendTo($("head")); var rules = ...;// code to create the text of the rules here if ($style[0].styleSheet) { // IE $style[0].styleSheet.cssText = rules.join(" "); } else { $style[0].appendChild(document.createTextNode(rules.join(" "))); } $style.bind('load', function() { functionThatExpectsTheStylesheet(); }); 

Et functionThatExpectsTheStylesheet tente de localiser l'objet de feuille de style actuel comme suit:

  var sheets = document.styleSheets; for (var i = 0; i < sheets.length; i++) { if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) { stylesheet = sheets[i]; break; } } 

Mais parfois même à ce moment-là, l'objet de la feuille de style n'est pas trouvé.

Voici donc ma question:

  1. L'événement de load ne garantit en fait pas que l'objet styleSheet soit disponible? Est-ce un bug?
  2. Dans le cas contraire, est-ce qu'il y a une autre condition que je peux utiliser ou que j'ai besoin de faire ceci en utilisant des délais d'attente?
  3. Ou est-ce qu'il y a un problème avec la façon dont je lierai l'événement de chargement et c'est mon problème?

Le chargement dynamique des feuilles de style CSS est encore une zone remplie de fonctionnalités de navigateur, malheureusement. Dans Webkit, les éléments <style> et <link> error événements de load et d' error lors du chargement de feuilles de style. Cependant, l'événement de load lui-même signifie uniquement que la ressource de feuille de style a été chargée, pas nécessairement qu'elle a été ajoutée à document.styleSheets .

Le chargeur require-css RequireJS traite de ce problème en ramifiant son mécanisme de chargement basé sur le sniffing userAgent (il est presque impossible de détecter de manière caractéristique si la <link> déclenchera son événement de load correctement). Spécifiquement pour Webkit, la détection consiste à utiliser setTimeout pour trouver quand l'objet StyleSheet a été attaché à document.styleSheets

 var webkitLoadCheck = function(link, callback) { setTimeout(function() { for (var i = 0; i < document.styleSheets.length; i++) { var sheet = document.styleSheets[i]; if (sheet.href == link.href) return callback(); } webkitLoadCheck(link, callback); }, 10); } 

Ainsi, alors que l'événement de load déclenchera sur Webkit, il n'est pas fiable pour pouvoir accéder à l'instance de StyleSheet correspondante. Actuellement, le seul moteur qui prend en load événements de load feuille de style est Firefox 18+.

Divulgation : je suis contributeur à exiger-css

Les références:

  1. Je pense que l'événement de chargement pourrait être déclenché plus tôt que la feuille de style css. Le problème se produira peut-être 1 fois 10 fois, mais ce n'est toujours pas la meilleure pratique et la manière la plus propre.

  2. SetTimeout est sûrement une bonne approche, car il peut être garanti que votre script charge le css en premier. Cependant, comme seule la balise de la feuille de style signifie que le lien est créé, le css doit d'abord être téléchargé, alors quelle que soit l'approche de ces deux que vous l'utilisez n'est jamais 100% garantie que votre css est chargé avant l'événement de chargement les feux.

Pour être sûr à 100% que tout fonctionne dans l'ordre correct, effectuer une fileread synchrone via AJAX ou un appel AJAX asynchrone avec une fonction helper qui vérifie si le CSS est prêt (de cette façon, vous ne garez pas le navigateur mais avez encore la capacité Pour vérifier si le fichier est chargé ou non). Une autre façon serait de simplement lier le mode d'emploi des feuilles de style: vous définissez les feuilles de style en premier et ensuite le code dans l'en-tête de votre fichier HTML.

Je n'avais pas l'air tellement profond dans les événements de chargement, mais il se pourrait que l'événement DOMContentLoaded déclenche uniquement lorsque CSS est chargé (recherche sur celui-ci)

Regardez ici une question similaire: l' événement jQuery qui déclenche après le chargement du CSS?

Je ne prétends pas avoir une solution, ni une explication du timing de chargement de Webkit, mais j'ai récemment eu un problème similaire, mais avec JS: je devais être absolument sûr que le framework jQuery était chargé avant d'inclure d'autres bibliothèques de mon Propre, qui dépendait de jQuery.

J'ai alors remarqué que l'événement de load de jQuery n'a pas été déclenché lorsque je l'attendais.

Pour ce qu'il vaut, voici comment j'ai résolu mon problème:

 var addedHead = window.document.createElement('link'); addedHead.async = true; addedHead.onload = addedHead.onreadystatechange = function () { if (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete') { callback(); addedHead.onload = addedHead.onreadystatechange = null; } }; //And then append it to the head tag 

Vous pourriez essayer!

Avoir le même besoin, en utilisant la détection de la règle du style pour vérifier si la feuille de style a été chargée.

 // Load CSS dynamically. There's no way to determine when stylesheet has been loaded // so we usingn hack - define `#my-css-loaded {position: absolute;}` rule in stylesheet // and the `callback` will be called when it's loaded. var loadCss = function(url, cssFileId, callback){ // CSS in IE can be added only with `createStyleSheet`. if(document.createStyleSheet) document.createStyleSheet(url) else $('<link rel="stylesheet" type="text/css" href="' + url + '" />').appendTo('head') // There's no API to notify when styles will be loaded, using hack to // determine if it's loaded or not. var $testEl = $('<div id="' + cssFileId + '" style="display: none;"></div>').appendTo('body') var checkIfStyleHasBeenLoaded = function(){ if($testEl.css('position') == 'absolute'){ $testEl.remove() callback() }else setTimeout(checkIfStyleHasBeenLoaded, 10) } setTimeout(checkIfStyleHasBeenLoaded, 0) }