无需页面滚动即可修改location.hash


148

我们有一些页面使用ajax来加载内容,并且在某些情况下需要深度链接到页面。与其链接到“用户”并告诉人们单击“设置”,不如将其链接到user.aspx#settings,这很有帮助。

为了使人们能够提供指向各节的正确链接(以获取技术支持等),我将其设置为在单击按钮时自动修改URL中的哈希。当然,唯一的问题是,发生这种情况时,它还会将页面滚动到该元素。

有没有办法禁用此功能?到目前为止,这是我目前的做法。

$(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;
    });
});

我曾希望这样做return false;可以阻止页面滚动-但这只会使链接根本不起作用。所以这只是现在注释掉了,所以我可以导航。

有任何想法吗?

Answers:


111

步骤1:您需要取消分散节点ID,直到设置了哈希值为止。这是通过在设置哈希值时从节点上删除ID,然后再将其重新添加来完成的。

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

第2步:某些浏览器会根据上次看到ID节点的位置触发滚动,因此您需要提供一些帮助。您需要div在视口顶部添加一个附加项,将其ID设置为哈希,然后将所有内容回滚:

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

第3步:将其包装在插件中,然后使用它代替写入location.hash...


1
不,步骤2中发生的事情是创建了一个隐藏的div并将其放置在滚动条的当前位置。视觉上与position:fixed / top:0相同。因此,滚动条将“移动”到当前所在的完全相同的位置。
博尔加尔

3
该解决方案确实工作得很好-但行:顶:$ .scroll()顶部+ '像素'应该是:顶部:$(窗口).scrollTop()+ '像素'。
马克·帕金斯

27
了解哪些浏览器需要此处的第2步将很有用。
djc

1
谢谢Borgar。尽管您在删除目标节点的ID之前附加了FX div,这使我感到困惑。这意味着在瞬间文档中有重复的ID。似乎是潜在的问题,或至少是举止不好;;
2012年

1
非常感谢,这对我也有帮助。但是,当执行此操作时,我也注意到了一个副作用:我经常通过锁定“鼠标中键”并简单地将鼠标移到要滚动的方向来滚动。在Google Chrome浏览器中,这会中断滚动流程。可能有一个不错的解决方法吗?
YMMD 2012年

99

使用history.replaceStatehistory.pushState*更改哈希。这不会触发跳转到关联的元素。

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

JSFiddle上的演示

*如果您希望历史向前和向后支持

历史行为

如果您正在使用history.pushState并且当用户使用浏览器的历史记录按钮(前进/后退)时不希望页面滚动,请查看实验性scrollRestoration设置(仅适用于Chrome 46+)

history.scrollRestoration = 'manual';

浏览器支持


5
replaceState可能是去这里的更好的方法。区别在于没有pushState添加项目到您的历史记录replaceState
Aakil Fernandes 2015年

谢谢@AakilFernandes,您是对的。我刚刚更新了答案。
HaNdTriX 2015年

body滚动的并不总是,有时是documentElement。看到迭戈·佩里尼(Diego Perini)的要旨
Matijs 2015年

1
对ie8 / 9有什么想法吗?
user151496 2015年

1
:@ user151496退房stackoverflow.com/revisions/...
HaNdTriX

90

我想我可能已经找到了一个相当简单的解决方案。问题在于URL中的哈希也是滚动到页面上的元素。如果我只是在哈希之前添加一些文本,那么它不再引用现有元素!

$(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;
    });
});

现在,URL page.aspx#btn_elementID在页面上显示为非真实ID。我只是删除“ btn_”并获取实际的元素ID


5
很好的解决方案。最轻松的。
Swader 2011年

请注意,如果由于某种原因您已经承诺使用page.aspx#elementID URL,则可以逆转此技术,并在所有ID之前添加“ btn_”
joshuahedlund 2012年

2
如果在CSS中使用:target伪选择器,则无法使用。
杰森·T·费瑟辛汉姆

1
爱这个解决方案!我们将URL哈希用于基于AJAX的导航,在ID之前加上/似乎合乎逻辑的内容。
Connell 2013年

3

您原始代码的摘要:

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

更改为:

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

这将不起作用,因为设置hash属性将导致页面仍然滚动。
jordanbtucker 2014年

3

最近,我正在构建一个依赖于window.location.hash维持状态的轮播,并发现Chrome和Webkit浏览器将在window.onhashchange事件触发时以笨拙的方式强制滚动(甚至到不可见的目标)。

甚至尝试注册停止传播的处理程序:

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

没有采取任何措施来停止默认的浏览器行为。我发现的解决方案window.history.pushState用于更改哈希,而不会触发不良的副作用。

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

然后,您可以同时监听正常的hashchange事件和my.hashchange

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

当然,my.hashchange这只是事件名称的一个示例
最高

2

好的,这是一个比较老的话题,但我认为我会加入,因为“正确”的答案在CSS中无法很好地工作。

该解决方案基本上可以防止 click事件移动页面,因此我们可以首先获取滚动位置。然后,我们手动添加哈希,浏览器将自动触发hashchange事件。我们捕获hashchange事件,然后滚动回到正确的位置。回调通过将散列黑客行为集中在一个地方来分隔并防止您的代码导致延迟。

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

您不需要hashchange,只需立即回滚
daniel.gindi 2014年

2

嗯,我的方法有些粗糙,但绝对可以使用。
只需将当前滚动位置存储在temp变量中,然后在更改哈希值后将其重置即可。:)

因此,对于原始示例:

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

这种方法的问题在于移动浏览器,您会注意到滚动被修改了
Tofandel 2015年

1

我认为这是不可能的。据我所知,浏览器唯一不滚动到更改的document.location.hash是该页面中是否不存在哈希。

本文与您的问题没有直接关系,但是讨论了浏览器在更改时的典型行为document.location.hash


1

如果在哈希解析器中使用hashchange事件,则可以防止对链接执行默认操作并更改location.hash添加一个字符以与元素的id属性具有差异

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

在此处添加此内容是因为较相关的问题都被标记为重复的内容,指向此处…

我的情况比较简单:

  • 用户点击链接(a[href='#something']
  • 点击处理程序可以: e.preventDefault()
  • 平滑滚动功能: $("html,body").stop(true,true).animate({ "scrollTop": linkoffset.top }, scrollspeed, "swing" );
  • 然后 window.location = link;

这样,将发生滚动,并且在更新位置时不会发生跳转。



0

这是我的启用历史记录的标签的解决方案:

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

并显示最后选择的标签:

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

请注意,*=filter电话。这是jQuery特有的东西,如果没有它,启用历史记录的选项卡将失败。


0

此解决方案在实际的scrollTop处创建一个div,并在更改哈希值后将其删除:

$('#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();
});

可选,在页面加载时触发锚事件:

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

0

我有一个更简单的方法适合我。基本上,请记住HTML中的哈希实际上是什么。这是指向名称标签的锚链接。这就是它滚动的原因...浏览器正在尝试滚动到锚链接。所以,给它一个!

  1. 在BODY标签下,输入您的版本:
 <a name="home"> </a> <a name="firstsection"> </a> <a name="secondsection"> </a> <a name="thirdsection"> </a>
  1. 用类而不是ID命名您的部分div。

  2. 在您的处理代码中,去除井号并用点代替:

    var trimPanel = loadhash.substring(1); //丢掉哈希

    var dotSelect ='。' + trimPanel; //用点替换哈希

    $(dotSelect).addClass(“ activepanel”)。show(); //显示与哈希关联的div。

最后,删除element.preventDefault或return:false并允许进行导航。窗口将保留在顶部,哈希将附加到地址栏网址,然后将打开正确的面板。


0

我认为您需要在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

如果在页面上您使用id作为锚点,并且在某些情况下,您希望用户将#something附加到网址末尾,并使用自己定义的动画将页面滚动到该#something部分javascript函数,hashchange事件侦听器将无法执行此操作。

例如,如果您只是在hashchange事件之后立即放置一个调试器,则是这样的(好吧,我使用jquery,但您明白了):

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

您会发现,一旦更改了网址并按下Enter键,页面便立即在相应的部分停止,只有在那之后,您自己定义的滚动功能才会被触发,并且它会滚动到该部分,看起来很坏。

我的建议是:

  1. 不要将id用作要滚动到的部分的锚点。

  2. 如果必须像我一样使用ID。请改用“ popstate”事件侦听器,它不会自动滚动到添加到url的那一部分,而是可以在popstate事件内调用自己定义的函数。

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

最后,您需要在自己定义的滚动功能中做一些技巧:

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

删除标签上的ID并将其重置。

这应该可以解决问题。


-5

仅在准备好文档时将此代码添加到jQuery中

参考: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;
      }
    }
  });
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.