web-dev-qa-db-fra.com

Empêcher le défilement du navigateur sur l'historique HTML5

Est-il possible d'empêcher le comportement par défaut de défilement du document lorsqu'un événement popstate se produit? 

Notre site utilise le défilement animé jQuery et History.js. Les changements d'état doivent faire défiler l'utilisateur vers différentes zones de la page, que ce soit via pushstate ou popstate. Le problème est que le navigateur restaure la position de défilement de l'état précédent automatiquement lorsqu'un événement popstate se produit.

J'ai essayé d'utiliser un élément de conteneur défini à 100% de la largeur et de la hauteur du document et de faire défiler le contenu à l'intérieur de ce conteneur. Le problème avec ce que j’ai trouvé est que cela ne semble pas être aussi lisse que de faire défiler le document; surtout si vous utilisez beaucoup de CSS3 comme les ombres de boîte et les dégradés.

J'ai également essayé de stocker la position de défilement du document lors d'un défilement initié par l'utilisateur et de le restaurer après que le navigateur a fait défiler la page (sous Popstate). Cela fonctionne bien dans Firefox 12, mais dans Chrome 19, il y a un scintillement dû au défilement et à la restauration de la page. Je suppose que cela a à voir avec un délai entre le défilement et le déclenchement de l'événement de défilement (où la position du défilement est restaurée).

Firefox fait défiler la page (et déclenche l'événement scroll) avant le déclenchement de Popstate, puis de Chrome Popstate, puis fait défiler le document.

Tous les sites que j'ai vus et qui utilisent l'API d'historique utilisent une solution similaire à celles décrites ci-dessus ou ignorent simplement le changement de position de défilement lorsqu'un utilisateur effectue un retour/un recul (par exemple, GitHub).

Est-il possible d'empêcher le défilement du document lors d'événements popstate?

72
if ('scrollRestoration' in history) {
  history.scrollRestoration = 'manual';
}

( Annoncé par Google le 2 septembre 2015)

Support du navigateur:

Chrome: pris en charge (depuis 46)

Firefox: supporté (depuis 46 ans)

IE/Edge: non pris en charge, demander de l'aide (puis laisser le lien dans le commentaire)

Opera: pris en charge (depuis 33)

Safari: pris en charge

Pour plus d'informations, voir Compatibilité du navigateur sur MDN .

52
Tamás Bolvári

C’est un problème signalé depuis plus d’un an avec le noyau de développeurs mozilla . Malheureusement, le ticket n'a pas vraiment progressé. Je pense que Chrome est identique: il n’existe aucun moyen fiable de régler la position de défilement onpopstate via js, car il s’agit d’un comportement de navigateur natif.

Il y a cependant de l'espoir pour l'avenir si vous regardez la spécification d'historique HTML5 qui souhaite explicitement que la position de défilement soit représentée sur l'objet d'état:

Les objets d'historique représentent l'historique de session de leur contexte de navigation sous forme de liste plate d'entrées d'historique de session. Chaque entrée d'historique de session comprend une URL et éventuellement un objet d'état. Elle peut en outre être associée à un titre, un objet Document, des données de formulaire, une position de défilement et d'autres informations.

Ceci, et si vous lisez les commentaires sur le ticket mozilla mentionné ci-dessus, donne une indication qu'il est possible que, dans un avenir proche, la position du défilement ne soit plus restaurée onpopstate, au moins pour les personnes utilisant pushState.

Malheureusement, jusque-là, la position de défilement est enregistrée lorsque pushState est utilisé, et replaceState ne remplace pas la position de défilement. Sinon, ce serait assez facile et vous pourriez utiliser replaceState pour définir la position de défilement actuelle à chaque fois que l'utilisateur a fait défiler la page (avec un gestionnaire de précaution onscroll).

Aussi, malheureusement, la spécification HTML5 ne spécifie pas quand exactement l'événement popstate doit être déclenché, elle dit simplement: «est déclenché dans certains cas lorsque vous accédez à une entrée d'historique de session», qui ne dit pas clairement si c'est avant ou après; si c'était toujours le cas auparavant, une solution permettant de gérer l'événement de défilement se produisant après le popstate serait possible.

Annuler l'événement de défilement?

En outre, il serait également facile, si l'événement de défilement était annulable, ce qui n'est pas le cas. Si c'était le cas, vous pourriez simplement annuler le premier événement de défilement d'une série (les événements de défilement utilisateur ressemblent à des lemmings, ils se présentent par dizaines, alors que l'événement de défilement déclenché par le repositionnement de l'historique est unique), et tout irait bien. 

Il n'y a pas de solution pour l'instant

Pour autant que je sache, la seule chose que je recommande pour l'instant est d'attendre que la spécification HTML5 soit pleinement mise en œuvre et de suivre le comportement du navigateur dans ce cas, cela signifie: animer le défilement lorsque le navigateur vous le permet. et laissez le navigateur repositionner la page en cas d’historique. La seule chose sur laquelle vous pouvez influencer la position est d’utiliser pushState lorsque la page est positionnée de manière à pouvoir revenir en arrière. Toute autre solution est liée à des bogues, soit à un navigateur trop spécifique, ou aux deux.

30
Beat Richartz

Vous allez devoir utiliser une sorte de navigateur horrible pour renifler ici. Pour Firefox, je voudrais aller avec votre solution de stocker la position de défilement et de le restaurer.

Je pensais avoir une bonne solution Webkit basée sur votre description, mais je viens d’essayer dans Chrome 21 et il semble que Chrome fait défiler en premier, puis déclenche l’événement popstate, puis déclenche l’événement de défilement. Mais pour référence, voici ce que je suis venu avec:

function noScrollOnce(event) {
    event.preventDefault();
    document.removeEventListener('scroll', noScrollOnce);
}
window.onpopstate = function () {
    document.addEventListener('scroll', noScrollOnce);
};​

La magie noire telle que prétendre que la page défile en déplaçant un élément absolute est également exclue par la vitesse de repeint de l’écran.

Donc, je suis sûr à 99% que la réponse est que vous ne pouvez pas, et vous allez devoir utiliser l'un des compromis que vous avez mentionné dans la question. Les deux navigateurs défilent avant que JavaScript sache quoi que ce soit à son sujet. JavaScript ne peut donc réagir qu'après l'événement. La seule différence est que Firefox ne peint pas l’écran tant que le code Javascript n’a pas été activé. C’est pourquoi il existe une solution viable dans Firefox mais pas dans WebKit.

4
Nathan MacInnes

Maintenant tu peux faire 

history.scrollRestoration = 'manual';

et cela devrait empêcher le défilement du navigateur. Cela ne fonctionne actuellement que dans Chrome 46 et plus, mais il semble que Firefox envisage également de le prendre en charge.

3
Roman Usherenko

La solution consiste à utiliser position: fixed et à spécifier top égal à la position de défilement de la page. 

Voici un exemple:

$(window).on('popstate', function()
{
   $('.yourWrapAroundAllContent').css({
      position: 'fixed',
      top: -window.scrollY
   });

   requestAnimationFrame(function()
   {
      $('.yourWrapAroundAllContent').css({
         position: 'static',
         top: 0
      });          
   });
});

Oui, vous recevez une barre de défilement clignotante, mais elle est moins perverse.

2
mshipov

Le correctif suivant devrait fonctionner dans tous les navigateurs.

Vous pouvez définir la position de défilement sur 0 pour l'événement de déchargement. Vous pouvez en savoir plus sur cet événement ici: https://developer.mozilla.org/en-US/docs/Web/Events/unload . Essentiellement, l'événement de déchargement se déclenche juste avant de quitter la page.

Définir scrollPosition sur 0 lors du déchargement signifie que, lorsque vous quittez la page avec un ensemble pushState, scrollPosition est défini sur 0. Lorsque vous revenez à cette page en rafraîchissant ou en appuyant sur la touche, elle ne défile pas automatiquement.

//Listen for unload event. This is triggered when leaving the page.
//Reference: https://developer.mozilla.org/en-US/docs/Web/Events/unload
window.addEventListener('unload', function(e) {
    //set scroll position to the top of the page.
    window.scrollTo(0, 0);
});
1
Ben Bud

Régler scrollRestoration sur manuel ne fonctionnait pas pour moi, voici ma solution.

window.addEventListener('popstate', function(e) {
    var scrollTop = document.body.scrollTop;
    window.addEventListener('scroll', function(e) {
        document.body.scrollTop = scrollTop;
    });
});
1
rames

Comme d'autres l'ont dit, il n'y a pas vraiment de moyen de le faire, seulement un vilain hacky. Suppression de l'écouteur d'événement de défilement n'a pas fonctionné pour moi, alors voici ma façon laide de le faire:

/// GLOBAL VARS ////////////////////////
var saveScrollPos = false;
var scrollPosSaved = window.pageYOffset;
////////////////////////////////////////

jQuery(document).ready(function($){

    //// Go back with keyboard shortcuts ////
    $(window).keydown(function(e){
        var key = e.keyCode || e.charCode;

        if((e.altKey && key == 37) || (e.altKey && key == 39) || key == 8)
        saveScrollPos = false;
    });
    /////////////////////////////////////////

    //// Go back with back button ////
    $("html").bind("mouseout",  function(){ saveScrollPos = false; });
    //////////////////////////////////

    $("html").bind("mousemove", function(){ saveScrollPos = true; });

    $(window).scroll(function(){
        if(saveScrollPos)
        scrollPosSaved = window.pageYOffset;
        else
        window.scrollTo(0, scrollPosSaved);
    });

});

Cela fonctionne dans Chrome, FF et IE (il clignote la première fois que vous revenez dans IE). Toutes les suggestions d'amélioration sont les bienvenues! J'espère que cela t'aides.

0
pmrotule

Créez un élément 'span' quelque part en haut de la page et définissez le focus sur ceci au chargement. Le navigateur fera défiler jusqu'à l'élément ciblé. Je comprends que ceci est une solution de contournement et se concentrer sur «span» ne fonctionne pas dans tous les navigateurs (uhmmm .. Safari). J'espère que cela t'aides.

0
Prashant Saraswat

Voici ce que j'ai implémenté sur un site qui souhaitait que la position du défilement se concentre sur un élément spécifique lorsque la poststate est déclenchée (bouton Précédent):

$(document).ready(function () {

     if (window.history.pushState) {
        //if Push supported - Push current page onto stack:
        window.history.pushState(null, document.title, document.location.href);
    }

    //add handler:
    $(window).on('popstate', PopStateHandler);
}

//fires when the back button is pressed:
function PopStateHandler(e) {
    emnt= $('#elementID');
    window.scrollTo(0, emnt.position().top);
    alert('scrolling to position');
}

Testé et fonctionne sous firefox. 

Chrome fera défiler jusqu'à la position, puis se repositionnera à son emplacement d'origine.

0
George Filippakos