2010-11-22 26 views
9

Problème:
fichiers js Charge de manière asynchrone, puis vérifiez si le dom est chargé avant le rappel de chargement des fichiers est exécuté.charge javascript async, puis vérifiez DOM chargé avant d'exécuter le rappel

éditer: Nous n'utilisons pas jQuery; Nous utilisons Prototype.
modifier: a ajouté d'autres commentaires à l'exemple de code.

Hé les gars,

Je suis en train de charger tous mes fichiers js afin de manière asynchrone les empêcher de bloquer le reste de la page. Mais quand les scripts se chargent et que le callback est appelé, j'ai besoin de savoir si le DOM a été chargé ou pas, donc je sais comment structurer le callback. Voir ci-dessous:

//load asynchronously 
(function(){ 
     var e = document.createElement('script'); 
     e.type = "text/javascript"; 
     e.async = true; 
     e.src = srcstr; 
     // a little magic to make the callback happen 
     if(navigator.userAgent.indexOf("Opera")){ 
      e.text = "initPage();"; 
     }else if(navigator.userAgent.indexOf("MSIE")){ 
      e.onreadystatechange = initPage; 
     }else{ 
      e.innerHTML = "initPage();"; 
     } 
     // attach the file to the document 
     document.getElementsByTagName('head')[0].appendChild(e); 
})(); 

initPageHelper = function(){ 
    //requires DOM be loaded 
} 

initPage = function(){ 
    if(domLoaded){ // if dom is already loaded, just call the function 
     initPageHelper(); 
    }else{ //if dom is not loaded, attach the function to be run when it does load 
     document.observe("dom:loaded", initPageHelper); 
    } 
} 

Le rappel est appelé correctement à cause de la magie dans les coulisses que vous pouvez en apprendre davantage sur de cette discussion Google: http://www.youtube.com/watch?v=52gL93S3usU&feature=related

Quelle est la meilleure méthode multi-navigateur pour demander si les DOM a déjà chargé?

EDIT
Voici la solution complète avec laquelle je suis allé.
J'ai inclus le prototype et le chargeur de script asynchrone en utilisant la méthode normale. La vie est tellement plus facile avec le prototype, donc je suis prêt à bloquer pour ce scénario.

<script type="text/javascript" src="prototype/prototype.js"></script> 
<script type="text/javascript" src="asyncLoader.js"></script> 

Et en fait, dans mon code, je minified les deux fichiers ci-dessus et les mettre ensemble dans un seul fichier pour réduire au minimum le temps de transfert et requêtes http.

Ensuite, je définis ce que je veux exécuter lorsque le DOM charge, puis appeler la fonction pour charger les autres scripts. De même, les demandes ci-dessus sont en fait compressées en une requête http à l'aide d'un minificateur. Ils sont laissés séparés ici pour la lisibilité. Il y a un extrait au bas de ce post montrant à quoi ressemble le code avec le minifier.

Le code pour asyncLoader.js est le suivant:

/** 
* Allows you to load js files asynchronously, with a callback that can be 
* called immediately after the script loads, OR after the script loads and 
* after the DOM is loaded. 
* 
* Prototype.js must be loaded first. 
* 
* For best results, create a regular script tag that calls a minified, combined 
* file that contains Prototype.js, and this file. Then all subsequent scripts 
* should be loaded using this function. 
* 
*/ 
var onload_queue = []; 
var dom_loaded = false; 
function loadScriptAsync(src, callback, run_immediately) { 
     var script = document.createElement('script'); 
     script.type = "text/javascript"; 
     script.async = true; 
     script.src = src; 
     if("undefined" != typeof callback){ 
      script.onload = function() { 
       if (dom_loaded || run_immediately) 
        callback(); 
       else 
        onload_queue.push(callback); 
       // clean up for IE and Opera 
       script.onload = null; 
       script.onreadystatechange = null; 
      }; 

      script.onreadystatechange = function() { 
      if (script.readyState == 'complete'){ 
       if (dom_loaded || run_immediately) 
        callback(); 
       else 
        onload_queue.push(callback); 
       // clean up for IE and Opera 
       script.onload = null; 
       script.onreadystatechange = null; 
      }else if(script.readyState == 'loaded'){ 
       eval(script); 
       if (dom_loaded || run_immediately) 
         callback(); 
       else 
        onload_queue.push(callback); 
       // clean up for IE and Opera 
       script.onload = null; 
       script.onreadystatechange = null; 
      } 
      }; 
     } 
     var head = document.getElementsByTagName('head')[0]; 
     head.appendChild(script); 
} 
document.observe("dom:loaded", function(){ 
    dom_loaded = true; 
    var len = onload_queue.length; 
    for (var i = 0; i < len; i++) { 
     onload_queue[i](); 
    } 
    onload_queue = null; 
}); 

j'ai ajouté la possibilité d'exécuter immédiatement un script, si vous avez des scripts qui ne reposent pas sur la page DOM étant complètement chargée.

Les demandes minified ressemblent vraiment:

<script type="text/javascript" src="/min/?b=javascript/lib&f=prototype/prototype.js,asyncLoader.js"></script> 
<script type="text/javascript"> initPage = function(e){...}</script> 
<script type="text/javascript"> 
    srcstr = "/min/?f=<?=implode(',', $js_files)?>"; 
    loadScriptAsync(srcstr, initPage); 
</script> 

Ils utilisent le plug-in à partir de: http://code.google.com/p/minify/

Merci pour l'aide !!
-K

+0

J'utilise jQuery événement $ (document) .ready(). La manière la plus facile, par navigateur que je connais. – Yoni

+0

Vous pouvez utiliser un indicateur que vous pouvez définir lorsque le DOM est chargé et vérifier ce drapeau dans votre rappel fn. – Rajat

+0

vous pouvez marquer une réponse comme acceptée si votre problème a été résolu. – galambalazs

Répondre

9

Ce dont vous avez besoin est une simple file d'attente de onload fonctions. Également s'il vous plaît évitez navigateur reniflant car il est instable et pas à l'épreuve du futur. Pour le code source complet voir le [Demo]

var onload_queue = []; 
var dom_loaded = false; 

function loadScriptAsync(src, callback) { 
    var script = document.createElement('script'); 
    script.type = "text/javascript"; 
    script.async = true; 
    script.src = src; 
    script.onload = script.onreadystatechange = function() { 
    if (dom_loaded) 
     callback(); 
    else 
     onload_queue.push(callback); 
    // clean up for IE and Opera 
    script.onload = null; 
    script.onreadystatechange = null; 
    }; 
    var head = document.getElementsByTagName('head')[0]; 
    head.appendChild(script); 
} 

function domLoaded() { 
    dom_loaded = true; 
    var len = onload_queue.length; 
    for (var i = 0; i < len; i++) { 
    onload_queue[i](); 
    } 
    onload_queue = null; 
}; 

// Dean's dom:loaded code goes here 
// do stuff 
domLoaded(); 

utilisation du test

loadScriptAsync(
    "http://code.jquery.com/jquery-1.4.4.js", 
    function() { 
     alert("script has been loaded"); 
    } 
); 
+0

mais que se passe-t-il si le rappel asynchrone retourne après le chargement de dom? – Ken

+0

le seul problème avec cette mise à jour est que je voulais aussi charger la bibliothèque prototype de manière asynchrone, ce qui signifie que document.observe() ne sera pas disponible. – Ken

+0

ce n'est pas un problème, utilisez juste http://dean.edwards.name/weblog/2006/06/again/ pour 'dom: loaded', même jQuery utilise une version modifiée de ceci. – galambalazs

0

Vous pouvez toujours placer votre script de chargement initial en bas, juste avant la balise de fermeture du corps.

+0

cela ne fonctionnera pas, comme je veux que mes scripts commencent à charger de manière asynchrone en haut de la page – Ken

+0

bien, en bas, vous pouvez ajouter une balise script qui définit simplement une variable globale "var loaded = true;" ou semblable. Ou il pourrait avoir un élément avec un ID, et vous pouvez vérifier pour cet élément existant. – rob

+0

Et pourquoi voulez-vous les charger en haut? Si vous les placez juste en bas, l'analyseur ira au-dessus du HTML, chargera les images non-bloquantes et luttera alors avec vos javascripts tout en montrant le reste de la page. Et comme il semble que vos scripts ont besoin d'un dom chargé de toute façon, pourquoi jouer avec ça? Un simple $ (document) .observe ("dom: loaded", function() {...}) fera le travail. – philgiese