Modification de location.hash sans défilement de page


148

Nous avons quelques pages utilisant ajax pour charger du contenu et il y a quelques occasions où nous devons créer un lien profond dans une page. Au lieu d'avoir un lien vers "Utilisateurs" et de dire aux gens de cliquer sur "Paramètres", il est utile de pouvoir associer des personnes à user.aspx # settings

Pour permettre aux gens de nous fournir des liens corrects vers des sections (pour le support technique, etc.), je l'ai configuré pour modifier automatiquement le hachage dans l'URL chaque fois qu'un bouton est cliqué. Le seul problème est bien sûr que lorsque cela se produit, cela fait également défiler la page jusqu'à cet élément.

Existe-t-il un moyen de désactiver cela? Voici comment je fais cela jusqu'à présent.

$(function(){
    //This emulates a click on the correct button on page load
    if(document.location.hash){
     $("#buttons li a").removeClass('selected');
     s=$(document.location.hash).addClass('selected').attr("href").replace("javascript:","");
     eval(s);
    }

    //Click a button to change the hash
    $("#buttons li a").click(function(){
            $("#buttons li a").removeClass('selected');
            $(this).addClass('selected');
            document.location.hash=$(this).attr("id")
            //return false;
    });
});

J'avais espéré que return false;cela empêcherait la page de défiler - mais cela empêche simplement le lien de fonctionner du tout. Donc c'est juste commenté pour l'instant afin que je puisse naviguer.

Des idées?

Réponses:


111

Étape 1: Vous devez désamorcer l'ID de nœud jusqu'à ce que le hachage ait été défini. Cela se fait en supprimant l'ID du nœud pendant la définition du hachage, puis en le rajoutant.

hash = hash.replace( /^#/, '' );
var node = $( '#' + hash );
if ( node.length ) {
  node.attr( 'id', '' );
}
document.location.hash = hash;
if ( node.length ) {
  node.attr( 'id', hash );
}

Étape 2: Certains navigateurs déclenchent le défilement en fonction de l'endroit où le nœud ID a été vu pour la dernière fois, vous devez donc les aider un peu. Vous devez ajouter un supplément diven haut de la fenêtre, définir son ID sur le hachage, puis tout annuler:

hash = hash.replace( /^#/, '' );
var fx, node = $( '#' + hash );
if ( node.length ) {
  node.attr( 'id', '' );
  fx = $( '<div></div>' )
          .css({
              position:'absolute',
              visibility:'hidden',
              top: $(document).scrollTop() + 'px'
          })
          .attr( 'id', hash )
          .appendTo( document.body );
}
document.location.hash = hash;
if ( node.length ) {
  fx.remove();
  node.attr( 'id', hash );
}

Étape 3: Enveloppez-le dans un plugin et utilisez-le au lieu d'écrire dans location.hash...


1
Non, ce qui se passe à l'étape 2, c'est qu'un div caché est créé et placé à l'emplacement actuel du défilement. C'est la même chose visuellement que position: fixe / haut: 0. Ainsi, la barre de défilement est "déplacée" exactement au même endroit où elle se trouve actuellement.
Borgar

3
Cette solution fonctionne en effet bien - cependant la ligne: top: $ .scroll (). Top + 'px' devrait être: top: $ (window) .scrollTop () + 'px'
Mark Perkins

27
Il serait utile de savoir quels navigateurs ont besoin de l'étape 2 ici.
djc

1
Merci Borgar. Cela me rend perplexe que vous ajoutiez le div fx avant de supprimer l'id du nœud cible. Cela signifie qu'il y a un instant dans lequel il y a des ID en double dans le document. Cela semble être un problème potentiel, ou du moins de mauvaises manières;)
Ben

1
Merci beaucoup, cela m'a également aidé. Mais j'ai aussi remarqué un effet secondaire lorsque cela est en action: je fais souvent défiler en verrouillant le "bouton central de la souris" et en déplaçant simplement la souris dans la direction que je veux faire défiler. Dans Google Chrome, cela interrompt le flux de défilement. Existe-t-il une solution de contournement sophistiquée pour cela?
JMMD

100

Utilisez history.replaceStateou history.pushState* pour changer le hachage. Cela ne déclenchera pas le saut vers l'élément associé.

Exemple

$(document).on('click', 'a[href^=#]', function(event) {
  event.preventDefault();
  history.pushState({}, '', this.href);
});

Démo sur JSFiddle

* Si vous voulez un support historique et arrière

Comportement historique

Si vous utilisez history.pushStateet que vous ne souhaitez pas que la page défile lorsque l'utilisateur utilise les boutons d'historique du navigateur (avant / arrière), consultez le scrollRestorationparamètre expérimental (Chrome 46+ uniquement) .

history.scrollRestoration = 'manual';

Prise en charge du navigateur


5
replaceStateest probablement la meilleure façon de procéder ici. La différence étant pushStateajoute un élément à votre historique alors que ce replaceStaten'est pas le cas.
Aakil Fernandes

Merci @AakilFernandes vous avez raison. Je viens de mettre à jour la réponse.
HaNdTriX

Ce n'est pas toujours le bodyqui défile, parfois c'est le documentElement. Voir l' essentiel de Diego Perini
Matijs

1
une idée sur ie8 / 9 ici?
user151496

1
@ user151496 consultez: stackoverflow.com/revisions/…
HaNdTriX

90

Je pense avoir trouvé une solution assez simple. Le problème est que le hachage dans l'URL est également un élément de la page vers lequel vous faites défiler. si je préfère juste du texte au hachage, maintenant il ne fait plus référence à un élément existant!

$(function(){
    //This emulates a click on the correct button on page load
    if(document.location.hash){
     $("#buttons li a").removeClass('selected');
     s=$(document.location.hash.replace("btn_","")).addClass('selected').attr("href").replace("javascript:","");
     eval(s);
    }

    //Click a button to change the hash
    $("#buttons li a").click(function(){
            $("#buttons li a").removeClass('selected');
            $(this).addClass('selected');
            document.location.hash="btn_"+$(this).attr("id")
            //return false;
    });
});

Maintenant, l'URL apparaît comme page.aspx#btn_elementIDn'étant pas un véritable identifiant sur la page. Je viens de supprimer "btn_" et j'obtiens l'ID d'élément réel


5
Excellente solution. Le plus indolore du lot.
Swader le

Notez que si vous êtes déjà engagé dans les URL page.aspx # elementID pour une raison quelconque, vous pouvez inverser cette technique et ajouter «btn_» à tous vos identifiants
joshuahedlund

2
Ne fonctionne pas si vous utilisez: des pseudo-sélecteurs cibles dans CSS.
Jason T Featheringham

1
J'adore cette solution! Nous utilisons le hachage d'URL pour la navigation basée sur AJAX, ajouter les ID avec un /semble logique.
Connell

3

Un extrait de votre code d'origine:

$("#buttons li a").click(function(){
    $("#buttons li a").removeClass('selected');
    $(this).addClass('selected');
    document.location.hash=$(this).attr("id")
});

Remplacez ceci par:

$("#buttons li a").click(function(e){
    // need to pass in "e", which is the actual click event
    e.preventDefault();
    // the preventDefault() function ... prevents the default action.
    $("#buttons li a").removeClass('selected');
    $(this).addClass('selected');
    document.location.hash=$(this).attr("id")
});

Cela ne fonctionnera pas car la définition de la hashpropriété entraînera quand même le défilement de la page.
jordanbtucker

3

J'ai récemment construit un carrousel qui repose sur window.location.hashle maintien de l'état et j'ai découvert que les navigateurs Chrome et Webkit forceraient le défilement (même vers une cible non visible) avec une secousse maladroite lorsque l' window.onhashchangeévénement est déclenché.

Même essayer d'enregistrer un gestionnaire qui arrête la propagation:

$(window).on("hashchange", function(e) { 
  e.stopPropogation(); 
  e.preventDefault(); 
});

N'a rien fait pour arrêter le comportement par défaut du navigateur. La solution que j'ai trouvée consistait window.history.pushStateà changer le hachage sans déclencher les effets secondaires indésirables.

 $("#buttons li a").click(function(){
    var $self, id, oldUrl;

    $self = $(this);
    id = $self.attr('id');

    $self.siblings().removeClass('selected'); // Don't re-query the DOM!
    $self.addClass('selected');

    if (window.history.pushState) {
      oldUrl = window.location.toString(); 
      // Update the address bar 
      window.history.pushState({}, '', '#' + id);
      // Trigger a custom event which mimics hashchange
      $(window).trigger('my.hashchange', [window.location.toString(), oldUrl]);
    } else {
      // Fallback for the poors browsers which do not have pushState
      window.location.hash = id;
    }

    // prevents the default action of clicking on a link.
    return false;
});

Vous pouvez ensuite écouter à la fois l'événement de hachage normal et my.hashchange:

$(window).on('hashchange my.hashchange', function(e, newUrl, oldUrl){
  // @todo - do something awesome!
});

bien sûr, my.hashchangec'est juste un exemple de nom d'événement
max

2

D'accord, c'est un sujet assez ancien, mais je pensais que j'interviendrais car la réponse `` correcte '' ne fonctionne pas bien avec CSS.

Cette solution empêche essentiellement l'événement de clic de déplacer la page afin que nous puissions obtenir la position de défilement en premier. Ensuite, nous ajoutons manuellement le hachage et le navigateur déclenche automatiquement un événement hashchange . Nous capturons l'événement hashchange et retournons à la bonne position. Un rappel sépare et empêche votre code de causer un retard en gardant votre hacking au même endroit.

var hashThis = function( $elem, callback ){
    var scrollLocation;
    $( $elem ).on( "click", function( event ){
        event.preventDefault();
        scrollLocation = $( window ).scrollTop();
        window.location.hash = $( event.target ).attr('href').substr(1);
    });
    $( window ).on( "hashchange", function( event ){
        $( window ).scrollTop( scrollLocation );
        if( typeof callback === "function" ){
            callback();
        }
    });
}
hashThis( $( ".myAnchor" ), function(){
    // do something useful!
});

Vous n'avez pas besoin du hashchange, faites simplement défiler vers l'arrière immédiatement
daniel.gindi

2

Euh, j'ai une méthode un peu rudimentaire mais qui fonctionne vraiment.
Il suffit de stocker la position de défilement actuelle dans une variable de température, puis de la réinitialiser après avoir modifié le hachage. :)

Donc, pour l'exemple d'origine:

$("#buttons li a").click(function(){
        $("#buttons li a").removeClass('selected');
        $(this).addClass('selected');

        var scrollPos = $(document).scrollTop();
        document.location.hash=$(this).attr("id")
        $(document).scrollTop(scrollPos);
});

Le problème avec cette méthode est qu'avec les navigateurs mobiles, vous pouvez remarquer que le défilement est modifié
Tofandel

1

Je ne pense pas que ce soit possible. Pour autant que je sache, la seule fois où un navigateur ne fait pas défiler jusqu'à une modification document.location.hashest si le hachage n'existe pas dans la page.

Cet article n'est pas directement lié à votre question, mais il traite du comportement typique du navigateur en cas de modificationdocument.location.hash


1

si vous utilisez l'événement hashchange avec l'analyseur de hachage, vous pouvez empêcher l'action par défaut sur les liens et changer d'emplacement.hash en ajoutant un caractère pour avoir une différence avec la propriété id d'un élément

$('a[href^=#]').on('click', function(e){
    e.preventDefault();
    location.hash = $(this).attr('href')+'/';
});

$(window).on('hashchange', function(){
    var a = /^#?chapter(\d+)-section(\d+)\/?$/i.exec(location.hash);
});

Parfait! Après cinq heures à essayer de résoudre le problème avec le rechargement du hachage ...: / C'était la seule chose qui fonctionnait !! Merci!!!
Igor Trindade

1

L'ajout de ceci ici car les questions les plus pertinentes ont toutes été marquées comme des doublons pointant ici…

Ma situation est plus simple:

  • l'utilisateur clique sur le lien ( a[href='#something'])
  • le gestionnaire de clics fait: e.preventDefault()
  • fonction smoothscroll: $("html,body").stop(true,true).animate({ "scrollTop": linkoffset.top }, scrollspeed, "swing" );
  • puis window.location = link;

De cette façon, le défilement se produit et il n'y a pas de saut lorsque l'emplacement est mis à jour.


0

L'autre façon de procéder consiste à ajouter un div masqué en haut de la fenêtre. Ce div reçoit ensuite l'ID du hachage avant que le hachage ne soit ajouté à l'url .... donc vous n'obtenez pas de défilement.


0

Voici ma solution pour les onglets activés pour l'historique:

    var tabContainer = $(".tabs"),
        tabsContent = tabContainer.find(".tabsection").hide(),
        tabNav = $(".tab-nav"), tabs = tabNav.find("a").on("click", function (e) {
                e.preventDefault();
                var href = this.href.split("#")[1]; //mydiv
                var target = "#" + href; //#myDiv
                tabs.each(function() {
                    $(this)[0].className = ""; //reset class names
                });
                tabsContent.hide();
                $(this).addClass("active");
                var $target = $(target).show();
                if ($target.length === 0) {
                    console.log("Could not find associated tab content for " + target);
                } 
                $target.removeAttr("id");
                // TODO: You could add smooth scroll to element
                document.location.hash = target;
                $target.attr("id", href);
                return false;
            });

Et pour afficher le dernier onglet sélectionné:

var currentHashURL = document.location.hash;
        if (currentHashURL != "") { //a tab was set in hash earlier
            // show selected
            $(currentHashURL).show();
        }
        else { //default to show first tab
            tabsContent.first().show();
        }
        // Now set the tab to active
        tabs.filter("[href*='" + currentHashURL + "']").addClass("active");

Notez le *=sur l' filterappel. C'est une chose spécifique à jQuery, et sans cela, vos onglets activés pour l'historique échoueront.


0

Cette solution crée un div au niveau du scrollTop réel et le supprime après avoir changé le hachage:

$('#menu a').on('click',function(){
    //your anchor event here
    var href = $(this).attr('href');
    window.location.hash = href;
    if(window.location.hash == href)return false;           
    var $jumpTo = $('body').find(href);
    $('body').append(
        $('<div>')
            .attr('id',$jumpTo.attr('id'))
            .addClass('fakeDivForHash')
            .data('realElementForHash',$jumpTo.removeAttr('id'))
            .css({'position':'absolute','top':$(window).scrollTop()})
    );
    window.location.hash = href;    
});
$(window).on('hashchange', function(){
    var $fakeDiv = $('.fakeDivForHash');
    if(!$fakeDiv.length)return true;
    $fakeDiv.data('realElementForHash').attr('id',$fakeDiv.attr('id'));
    $fakeDiv.remove();
});

facultatif, déclenchant un événement d'ancrage au chargement de la page:

$('#menu a[href='+window.location.hash+']').click();

0

J'ai une méthode plus simple qui fonctionne pour moi. En gros, rappelez-vous ce qu'est réellement le hachage en HTML. C'est un lien d'ancrage vers une étiquette de nom. C'est pourquoi il défile ... le navigateur tente de faire défiler jusqu'à un lien d'ancrage. Alors, donnez-lui un!

  1. Juste sous la balise BODY, mettez votre version de ceci:
 <a name="home"> </a> <a name="firstsection"> </a> <a name="secondsection"> </a> <a name="thirdsection"> </a>
  1. Nommez vos divisions de section avec des classes au lieu d'ID.

  2. Dans votre code de traitement, supprimez la marque de hachage et remplacez-la par un point:

    var trimPanel = loadhash.substring (1); // perdre le hachage

    var dotSelect = '.' + trimPanel; // remplace le hachage par un point

    $ (dotSelect) .addClass ("activepanel"). show (); // affiche le div associé au hachage.

Enfin, supprimez element.preventDefault ou return: false et laissez la navigation se produire. La fenêtre restera en haut, le hachage sera ajouté à l'URL de la barre d'adresse et le panneau approprié s'ouvrira.


0

Je pense que vous devez réinitialiser le défilement à sa position avant le changement de hachage.

$(function(){
    //This emulates a click on the correct button on page load
    if(document.location.hash) {
        $("#buttons li a").removeClass('selected');
        s=$(document.location.hash).addClass('selected').attr("href").replace("javascript:","");
        eval(s);
    }

    //Click a button to change the hash
    $("#buttons li a").click(function() {
            var scrollLocation = $(window).scrollTop();
            $("#buttons li a").removeClass('selected');
            $(this).addClass('selected');
            document.location.hash = $(this).attr("id");
            $(window).scrollTop( scrollLocation );
    });
});

0

Si sur votre page vous utilisez id comme sorte de point d'ancrage, et que vous avez des scénarios dans lesquels vous voulez que les utilisateurs ajoutent # quelque chose à la fin de l'url et que la page défile jusqu'à cette # section quelque chose en utilisant votre propre animation définie javascript, l'écouteur d'événements hashchange ne pourra pas le faire.

Si vous mettez simplement un débogueur immédiatement après l'événement hashchange, par exemple, quelque chose comme ça (enfin, j'utilise jquery, mais vous voyez le point):

$(window).on('hashchange', function(){debugger});

Vous remarquerez que dès que vous changez votre URL et appuyez sur le bouton Entrée, la page s'arrête immédiatement à la section correspondante, seulement après cela, votre propre fonction de défilement définie sera déclenchée, et elle défile en quelque sorte vers cette section, qui ressemble très mauvais.

Ma suggestion est:

  1. n'utilisez pas id comme point d'ancrage vers la section vers laquelle vous voulez faire défiler.

  2. Si vous devez utiliser une pièce d'identité, comme moi. Utilisez plutôt l'écouteur d'événement 'popstate', il ne défilera pas automatiquement jusqu'à la section même que vous ajoutez à l'url, à la place, vous pouvez appeler votre propre fonction définie dans l'événement popstate.

    $(window).on('popstate', function(){myscrollfunction()});

Enfin, vous devez faire un petit tour dans votre propre fonction de défilement définie:

    let hash = window.location.hash.replace(/^#/, '');
    let node = $('#' + hash);
    if (node.length) {
        node.attr('id', '');
    }
    if (node.length) {
        node.attr('id', hash);
    }

supprimez l'identifiant de votre balise et réinitialisez-le.

Cela devrait faire l'affaire.


-5

N'ajoutez ce code dans jQuery que lorsque le document est prêt

Réf: http://css-tricks.com/snippets/jquery/smooth-scrolling/

$(function() {
  $('a[href*=#]:not([href=#])').click(function() {
    if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
      var target = $(this.hash);
      target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
      if (target.length) {
        $('html,body').animate({
          scrollTop: target.offset().top
        }, 1000);
        return false;
      }
    }
  });
});
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.