web-dev-qa-db-fra.com

Modification de location.hash sans défilement de page

Quelques pages utilisent ajax pour charger du contenu et il est parfois nécessaire de créer des liens profonds vers une page. Au lieu d’avoir un lien vers "Utilisateurs" et de demander aux utilisateurs de cliquer sur "paramètres", il est utile de pouvoir associer des personnes à ser.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 de l'URL chaque fois qu'un bouton est cliqué. Bien entendu, le seul problème est que, lorsque cela se produit, la page est déplacée sur cet élément.

Y at-il un moyen de désactiver cela? Vous trouverez ci-dessous comment je le fais 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é le return false; arrêterait le défilement de la page, mais le lien ne fonctionnerait plus du tout. Donc, c'est juste commenté pour le moment afin que je puisse naviguer.

Des idées?

141
CBarr

Je pense avoir peut-être trouvé une solution assez simple. Le problème est que le hachage dans l'URL est également un élément de la page sur lequel vous faites défiler. si je prends juste du texte dans le hachage, 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_elementID qui n'est pas un identifiant réel sur la page. Je supprime simplement "btn_" et récupère l'identifiant de l'élément

89
CBarr

Étape 1: Vous devez désamorcer l’ID du nœud jusqu’à ce que le hachage soit défini. Pour ce faire, supprimez l'ID du nœud lors de la définition du hachage, puis rajoutez-le.

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éclencheront le défilement en fonction du dernier emplacement du nœud ID. Vous devez donc les aider un peu. Vous devez ajouter un div supplémentaire en haut de la fenêtre, définir son identifiant sur le hachage, puis annuler tout:

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: Emballez-le dans un plugin et utilisez-le au lieu d’écrire dans location.hash...

110
Borgar

Utilisation history.replaceState ou history.pushState * pour changer le hachage. Cela ne déclenchera pas le saut à l'élément associé.

Exemple

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

Démo sur JSFiddle

* Si vous souhaitez un support historique et aval de l'historique

Comportement de l'histoire

Si vous utilisez history.pushState et vous ne voulez pas que la page défile lorsque l’utilisateur utilise les boutons d’historique du navigateur (avant/arrière), consultez le paramètre expérimental scrollRestoration (Chrome 46+ uniquement). ) .

history.scrollRestoration = 'manual';

Prise en charge du navigateur

86
HaNdTriX

Je construisais récemment un carrousel qui repose sur window.location.hash pour maintenir l’état et faire la découverte que Chrome et les navigateurs Webkit forceront le défilement (même sur une cible non visible)) avec un réflexe maladroit lorsque le window.onhashchange événement est déclenché.

Même en essayant d’enregistrer un gestionnaire qui stoppe la propogation:

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

N'a rien fait pour arrêter le comportement du navigateur par défaut. La solution que j'ai trouvée utilisait window.history.pushState pour modifier 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 alors écouter l’événement hashchange normal et my.hashchange:

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

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")
});

Changer ceci en:

$("#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")
});
3
Daniel

Ok, c’est un sujet plutôt ancien, mais j’ai pensé que j’entrerais dans les détails car la réponse "correcte" ne fonctionne pas bien avec CSS.

Cette solution en gros empêche l'événement click 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 revenons à la position correcte. Un rappel sépare et empêche votre code de causer un retard en gardant votre hachage de hachage 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!
});
2
Egor Kloos

Euh, j'ai une méthode un peu rudique mais qui fonctionne bien.
Enregistrez simplement la position de défilement actuelle dans une variable temp, puis réinitialisez-la après avoir modifié le hachage. :)

Donc pour l'exemple original:

$("#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);
});
2
Jan Paepke

si vous utilisez un événement hashchange avec un analyseur de hachage, vous pouvez empêcher l'action par défaut sur les liens et modifier location.hash en ajoutant un caractère pour qu'il soit différent de 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);
});
1
polkila

Ajoutons ceci ici parce que 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" );
  • alorswindow.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.

1
ptim

Je ne pense pas que ce soit possible. Autant que je sache, la seule fois où un navigateur ne fait pas défiler vers un document.location.hash Modifié est 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 consistant à modifier document.location.hash

1
Ben S

L'autre façon de faire est d'ajouter un div qui est caché en haut de la fenêtre. L'identifiant du hachage est ensuite attribué à cette div avant que celui-ci ne soit ajouté à l'URL .... afin que vous n'ayez pas un parchemin.

0
Mike Rifgin

Voici ma solution pour les onglets activés par 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");

Noter la *= sur l’appel filter. Ceci est une opération spécifique à jQuery, et sans cela, vos onglets activés pour l'historique échoueront.

0
user1429980

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

$(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
iProDev

Cette solution crée un div sur le scrollTop réel et le supprime après la modification du 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();
});

optionnel, événement déclencheur déclencheur au chargement de la page:

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

J'ai une méthode plus simple qui fonctionne pour moi. Fondamentalement, rappelez-vous ce que le hachage est réellement en HTML. C'est un lien d'ancrage à une balise Name. C'est pourquoi il fait défiler ... le navigateur tente de faire défiler vers 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 divs de section avec des classes plutôt que des identifiants.

  2. Dans votre code de traitement, décollez le signe dièse et remplacez-le par un point:

 var trimPanel = loadhash.substring (1); // perd le hachage 
 
 var dotSelect = '.' + trimPanel; // remplace le hachage par un point 
 
 $ (dotSelect) .addClass ("activepanel"). show (); // affiche le div associé au hash. 

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

0
ColdSharper

Si sur votre page, vous utilisez id comme une sorte de point d'ancrage et si vous souhaitez que les utilisateurs ajoutent #something à la fin de l'URL et que la page défile jusqu'à cette section #something en utilisant votre propre animation définie. fonction javascript, hashchange event listener 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 ceci (enfin, j'utilise jquery, mais vous obtenez le point):

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

Vous remarquerez que dès que vous modifiez votre URL et que vous appuyez sur le bouton Entrée, la page s'arrête immédiatement sur la section correspondante. Seulement après cela, votre propre fonction de défilement définie sera déclenchée et une sorte de défilement vers cette section, qui a très mauvais.

Ma suggestion est:

  1. n'utilisez pas id comme point d'ancrage vers la section où vous souhaitez faire défiler.

  2. Si vous devez utiliser un identifiant, comme moi. Utilisez l'écouteur d'événements 'popstate' à la place, il ne fera pas automatiquement défiler jusqu'à la section que vous ajoutez à l'URL, vous pouvez appeler votre propre fonction définie à l'intérieur de l'événement popstate.

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

Enfin, vous devez faire un 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);
    }

effacez l'identifiant de votre tag et réinitialisez-le.

Cela devrait faire l'affaire.

0
Shuwei