如何在不跳转文档的情况下更新window.location.hash?

193

我在我的网站上设置了一个滑动面板。

当它完成动画后,我会像这样设置哈希值

function() {
   window.location.hash = id;
}

(这是一个回调函数,id 是之前分配的。)

这个功能非常好用,让用户能够收藏面板,并且也适用于没有 JavaScript 版本的情况。

然而,当我更新哈希值后,浏览器会跳转到相应位置。我想这是预期行为。

我的问题是:如何防止这种情况发生?即如何更改窗口的哈希值,但若哈希值存在时使浏览器滚动到元素上?有一种类似 event.preventDefault() 的方法吗?

我正在使用 jQuery 1.4 和 scrollTo 插件

非常感谢!

更新

下面是更改面板的代码:

$('#something a').click(function(event) {
    event.preventDefault();
    var link = $(this);
    var id = link[0].hash;

    $('#slider').scrollTo(id, 800, {
        onAfter: function() {

            link.parents('li').siblings().removeClass('active');
            link.parent().addClass('active');
            window.location.hash = id;

            }
    });
});

2
我假设你已经尝试过 event.preventDefault() :) - Marko
@Marko Ivanovski 我不认为这很相关,但我会尽力而为。 - alex
@alex 嗯,如果我们看不到你的代码,就无法告诉你在哪里放置 preventDefault - Gareth
3
@Gareth,我认为没有适合的位置,因为只要我更新哈希值,它就会发生。 - alex
可能是重复的问题:修改document.location.hash而不导致页面滚动 - Francisco Presencia
显示剩余2条评论
9个回答

308

在现代浏览器上可以使用历史记录API,对于旧版本的浏览器则需要使用替代方案:

if(history.pushState) {
    history.pushState(null, null, '#myhash');
}
else {
    location.hash = '#myhash';
}

感谢Lea Verou

的贡献。


11
请注意,pushState 方法具有副作用,会将状态添加到浏览器历史记录堆栈中。换句话说,当用户单击后退按钮时,除非您还添加了 popstate 事件监听器,否则什么也不会发生。 - David Cook
42
@DavidCook - 我们也可以使用 history.replaceState(对于哈希值的更改可能更合理),以避免需要一个 popstate 事件监听器。 - Jack
这对我有效。我喜欢历史更改的效果。我想要补充一点,这不会触发“hashchange”事件。那是我不得不解决的问题。 - Jordan
1
警告!这不会触发“hashchange”事件。 - santiago arizti
1
不需要使用history.pushState来解决这个问题。如果你的:target元素具有position: fixed,它将从正常流中移除,因此没有地方可以滚动。然后,你可以使用window.history.hash = 'foo'来分配哈希值,窗口将保持不变。 - vhs
显示剩余2条评论

56

问题在于你正在将window.location.hash设置为元素的ID属性。无论你是否使用"preventDefault()",浏览器都会跳转到该元素,这是预期的行为。

解决此问题的一种方法是在哈希前面添加任意值,例如:

window.location.hash = 'panel-' + id.replace('#', '');

然后,您需要做的就是在页面加载时检查前缀哈希。作为额外的奖励,您甚至可以平滑地滚动到它,因为现在您控制哈希值......

$(function(){
    var h = window.location.hash.replace('panel-', '');
    if (h) {
        $('#slider').scrollTo(h, 800);
    }
});

如果你需要这个功能一直正常工作(而不仅仅在初始页面加载时),你可以使用一个函数来监视哈希值的变化,并即时跳转到正确的元素:

var foundHash;
setInterval(function() {
    var h = window.location.hash.replace('panel-', '');
    if (h && h !== foundHash) {
        $('#slider').scrollTo(h, 800);
        foundHash = h;
    }
}, 100);

3
这是唯一一个真正回答问题的解决方案——允许哈希而无需跳跃。 - amosmos
这真正回答了问题,解释了为什么要添加和删除哈希的任意值,以避免按照默认浏览器行为进行跳转。 - lowtechsun
3
好的解决方案,但它削弱了原帖的解决方案,因为它否定了将节段加为书签的用途。 - MikeyBeLike

33

简陋而不堪的解决方案..使用丑陋的 #! 样式。

设置方法:

window.location.hash = '#!' + id;

阅读方式:

id = window.location.hash.replace(/^#!/, '');

由于它与页面中的锚点或ID不匹配,因此它不会跳转。


4
我需要保持与IE 7和一些移动站点版本的兼容性,所以这个解决方案对我来说最干净。通常我会考虑使用pushState解决方案。 - Eric Goodwin
2
使用以下代码设置哈希值:window.location.hash = '#/' + id; 然后用以下代码替换它:window.location.hash.replace(/^#\//, '#'); 这样可以使URL更加美观 -> projects/#/tab1 - braitsch

12

我使用了Attila Fulop (Lea Verou)针对现代浏览器的解决方案和Gavin Brock针对旧浏览器的解决方案的组合,如下所示:

if (history.pushState) {
    // IE10, Firefox, Chrome, etc.
    window.history.pushState(null, null, '#' + id);
} else {
    // IE9, IE8, etc
    window.location.hash = '#!' + id;
}
根据Gavin Brock观察,要夺回id,您需要按照以下方法处理字符串(在这种情况下,字符串可以带有或不带有"!"):
id = window.location.hash.replace(/^#!?/, '');

在此之前,我尝试了类似于用户706270提出的解决方案,但它与Internet Explorer不兼容:由于其JavaScript引擎速度不够快,你会注意到滚动条的增加和减少,这会产生不好的视觉效果。


12

为什么不获取当前的滚动位置,将其存入一个变量中,然后分配哈希并将页面滚动恢复到原来的位置:

var yScroll=document.body.scrollTop;
window.location.hash = id;
document.body.scrollTop=yScroll;

这应该可以正常工作


2
嗯,这段时间一直能用,但在过去几个月里,在火狐浏览器上突然就不能用了... - matchew

4
这个解决方案对我很有用。
设置location.hash的问题在于,如果页面上找到了该id,则页面会跳转到该id。 window.history.pushState的问题在于,每次用户点击选项卡时,它都会向历史记录中添加一个条目。然后当用户点击back按钮时,他们回到先前的选项卡。(这可能是您想要的,也可能不是我想要的)。
对我来说,replaceState是更好的选择,因为它只替换当前历史记录,所以当用户点击back按钮时,他们回到之前的页面。
$('#tab-selector').tabs({
  activate: function(e, ui) {
    window.history.replaceState(null, null, ui.newPanel.selector);
  }
});

在MDN上查看历史API文档


4
这个解决方案对我有用
// store the currently selected tab in the hash value
    if(history.pushState) {
        window.history.pushState(null, null, '#' + id);
    }
    else {
        window.location.hash = id;
    }

// on load of the page: switch to the currently selected tab
var hash = window.location.hash;
$('#myTab a[href="' + hash + '"]').tab('show');

我的完整js代码如下:

$('#myTab a').click(function(e) {
  e.preventDefault();
  $(this).tab('show');
});

// store the currently selected tab in the hash value
$("ul.nav-tabs > li > a").on("shown.bs.tab", function(e) {
  var id = $(e.target).attr("href").substr(1);
    if(history.pushState) {
        window.history.pushState(null, null, '#' + id);
    }
    else {
        window.location.hash = id;
    }
   // window.location.hash = '#!' + id;
});

// on load of the page: switch to the currently selected tab
var hash = window.location.hash;
// console.log(hash);
$('#myTab a[href="' + hash + '"]').tab('show');

1
我不确定您是否能够更改原始元素,但是是否考虑使用诸如 data-id 这样的其他属性代替 id 属性呢?然后只需读取 data-id 的值作为哈希值,它就不会跳转。

0
当使用Laravel框架时,我在使用route->back()函数时遇到了一些问题,因为它会擦除我的哈希。为了保留我的哈希,我创建了一个简单的函数:
$(function() {   
    if (localStorage.getItem("hash")    ){
     location.hash = localStorage.getItem("hash");
    }
}); 

然后我在另一个JS函数中这样设置它:

localStorage.setItem("hash", myvalue);

您可以随意命名本地存储的值;我的命名为hash

因此,如果在PAGE1上设置了哈希值,然后导航到PAGE2;当您在PAGE2上单击“返回”时,哈希值将在PAGE1上重新创建。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接