web-dev-qa-db-fra.com

Popstate au chargement de la page dans Chrome

J'utilise l'historique API pour mon application Web et j'ai un problème. Je fais des appels Ajax pour mettre à jour certains résultats sur la page et utilise history.pushState () pour mettre à jour la barre d’emplacement du navigateur sans recharger la page. Ensuite, bien sûr, j'utilise window.popstate afin de restaurer l'état précédent lorsque l'utilisateur clique sur le bouton Précédent.

Le problème est connu - Chrome et Firefox traitent cet événement popstate différemment. Bien que Firefox ne le déclenche pas lors du premier chargement, Chrome le fait. J'aimerais avoir le style Firefox et ne pas déclencher l'événement au chargement car il ne fait que mettre à jour les résultats avec exactement les mêmes résultats au chargement. Existe-t-il une solution de contournement sauf l'utilisation de History.js ? vous n'avez pas envie de l'utiliser - il a besoin de beaucoup trop de bibliothèques JS et, comme j'ai besoin qu'il soit implémenté dans un CMS avec déjà trop de JS, j'aimerais minimiser les JS que je mets.

Donc, aimerait savoir s’il existe un moyen de créer Chrome ne déclenche pas "popstate" au chargement ou si quelqu'un essaie peut-être d'utiliser History.js lorsque toutes les bibliothèques sont mélangées en une seule. fichier.

94
spliter

Dans Google Chrome dans la version 19, la solution de @spliter ne fonctionnait plus. Comme @johnnymire l’a signalé, history.state in Chrome 19 existe, mais il est nul.

Ma solution consiste à ajouter window.history.state! == null dans la vérification de l'existence d'un état dans window.history:

var popped = ('state' in window.history && window.history.state !== null), initialURL = location.href;

Je l'ai testé dans tous les principaux navigateurs et dans Chrome versions 19 et 18. Il semble que cela fonctionne.

49
Pavel Linkesch

Au cas où vous ne voudriez pas prendre de mesures spéciales pour chaque gestionnaire ajouté à onpopstate, ma solution pourrait vous intéresser. Un gros avantage de cette solution est également que les événements onpopstate peuvent être gérés avant la fin du chargement de la page.

Il suffit d’exécuter ce code une fois avant d’ajouter des gestionnaires onpopstate et tout devrait fonctionner comme prévu (comme dans Mozilla ^^) .

(function() {
    // There's nothing to do for older browsers ;)
    if (!window.addEventListener)
        return;
    var blockPopstateEvent = document.readyState!="complete";
    window.addEventListener("load", function() {
        // The timeout ensures that popstate-events will be unblocked right
        // after the load event occured, but not in the same event-loop cycle.
        setTimeout(function(){ blockPopstateEvent = false; }, 0);
    }, false);
    window.addEventListener("popstate", function(evt) {
        if (blockPopstateEvent && document.readyState=="complete") {
            evt.preventDefault();
            evt.stopImmediatePropagation();
        }
    }, false);
})();

Comment ça marche:

Chrome, Safari et probablement d'autres navigateurs Webkit déclenchent l'événement onpopstate lorsque le document a été chargé. Ceci n'est pas prévu, nous bloquons donc les événements Popstate jusqu'au premier cycle de boucle d'événement après le chargement du document. Cette opération est effectuée par les appels preventDefault et stopImmediatePropagation (contrairement à stopPropagation, stopImmediatePropagation arrête instantanément tous les appels du gestionnaire d'événements).

Toutefois, comme l'attribut readyState du document est déjà "terminé" lorsque Chrome déclenche une erreur surpopstate, nous autorisons les événements opopstate, qui ont été déclenchés avant le chargement du document, pour autoriser les appels onpopstate avant le document. été chargé.

Mise à jour du 2014-04-23: - Correction d'un bug bloquant le blocage d'événements Popstate si le script était exécuté après le chargement de la page.

30
Torben

L'utilisation de setTimeout n'est pas une solution correcte car vous ne savez pas combien de temps il faudra pour que le contenu soit chargé. Il est donc possible que l'événement popstate soit émis après le délai.

Voici ma solution: https://Gist.github.com/3551566

/*
* Necessary hack because WebKit fires a popstate event on document load
* https://code.google.com/p/chromium/issues/detail?id=63040
* https://bugs.webkit.org/process_bug.cgi
*/
window.addEventListener('load', function() {
  setTimeout(function() {
    window.addEventListener('popstate', function() {
      ...
    });
  }, 0);
});
28
Sonny Piers

La solution a été trouvée dans jquery.pjax.js lignes 195-225:

// Used to detect initial (useless) popstate.
// If history.state exists, assume browser isn't going to fire initial popstate.
var popped = ('state' in window.history), initialURL = location.href


// popstate handler takes care of the back and forward buttons
//
// You probably shouldn't use pjax on pages with other pushState
// stuff yet.
$(window).bind('popstate', function(event){
  // Ignore inital popstate that some browsers fire on page load
  var initialPop = !popped && location.href == initialURL
  popped = true
  if ( initialPop ) return

  var state = event.state

  if ( state && state.pjax ) {
    var container = state.pjax
    if ( $(container+'').length )
      $.pjax({
        url: state.url || location.href,
        fragment: state.fragment,
        container: container,
        Push: false,
        timeout: state.timeout
      })
    else
      window.location = location.href
  }
})
25
spliter

Une solution plus directe que la réimplémentation de pjax est de définir une variable sur pushState et de vérifier la variable sur popState, de sorte que le popState initial ne se déclenche pas de manière irrégulière à la charge (pas une solution spécifique à jQuery, mais uniquement pour les événements):

$(window).bind('popstate', function (ev){
  if (!window.history.ready && !ev.originalEvent.state)
    return; // workaround for popstate on load
});

// ... later ...

function doNavigation(nextPageId) {
  window.history.ready = true;

  history.pushState(state, null, 'content.php?id='+ nextPageId); 
  // ajax in content instead of loading server-side
}
14
Tom McKenzie

L'événement initial onpopstate de Webkit n'a pas d'état attribué. Vous pouvez donc l'utiliser pour vérifier le comportement indésirable:

window.onpopstate = function(e){
    if(e.state)
        //do something
};

Une solution complète, permettant de revenir à la page d'origine, s'appuierait sur cette idée:

<body onload="init()">
    <a href="page1" onclick="doClick(this); return false;">page 1</a>
    <a href="page2" onclick="doClick(this); return false;">page 2</a>
    <div id="content"></div>
</body>

<script>
function init(){
   openURL(window.location.href);
}
function doClick(e){
    if(window.history.pushState)
        openURL(e.getAttribute('href'), true);
    else
        window.open(e.getAttribute('href'), '_self');
}
function openURL(href, Push){
    document.getElementById('content').innerHTML = href + ': ' + (Push ? 'user' : 'browser'); 
    if(window.history.pushState){
        if(Push)
            window.history.pushState({href: href}, 'your page title', href);
        else
            window.history.replaceState({href: href}, 'your page title', href);
    }
}
window.onpopstate = function(e){
    if(e.state)
        openURL(e.state.href);
};
</script>

Bien que cela puisse toujours se déclencher deux fois (avec une navigation astucieuse), il peut être manipulé simplement avec une vérification par rapport au href précédent.

4
som

Ceci est ma solution de contournement.

window.setTimeout(function() {
  window.addEventListener('popstate', function() {
    // ...
  });
}, 1000);
3
Baggz

Voici ma solution:

var _firstload = true;
$(function(){
    window.onpopstate = function(event){
        var state = event.state;

        if(_firstload && !state){ 
            _firstload = false; 
        }
        else if(state){
            _firstload = false;
            // you should pass state.some_data to another function here
            alert('state was changed! back/forward button was pressed!');
        }
        else{
            _firstload = false;
            // you should inform some function that the original state returned
            alert('you returned back to the original state (the home state)');
        }
    }
})   
1
prograhammer

Le meilleur moyen d'obtenir Chrome de ne pas déclencher le popstate lors du chargement d'une page est de voter à la hausse https://code.google.com/p/chromium/issues/detail?) id = 6304 . Ils savent que Chrome n'est pas conforme à la spécification HTML5 depuis deux ans et ne l'a toujours pas corrigée!

0
Dave

En cas d'utilisation event.state !== null retourner dans l'historique à la première page chargée ne fonctionnera pas dans les navigateurs non mobiles. J'utilise sessionStorage pour marquer quand la navigation ajax commence réellement.

history.pushState(url, null, url);
sessionStorage.ajNavStarted = true;

window.addEventListener('popstate', function(e) {
    if (sessionStorage.ajNavStarted) {
        location.href = (e.state === null) ? location.href : e.state;
    }
}, false);
0
ilusionist