提问者:小点点

防止浏览器在HTML5历史popstate上滚动


当popstate事件发生时,是否可以防止滚动文档的默认行为?

我们的网站使用jQuery动画滚动和历史. js,状态更改应该通过pushstate或popstate将用户滚动到页面的不同区域。问题是当popstate事件发生时,浏览器会自动恢复先前状态的滚动位置。

我尝试过使用设置为文档100%宽度和高度的容器元素并滚动该容器内的内容。我发现的问题是它似乎没有滚动文档那么流畅;特别是如果使用大量css3,如box阴影和渐变。

我还尝试在用户启动滚动期间存储文档的滚动位置,并在浏览器滚动页面(在popstate上)后恢复它。这在Firefox 12中效果很好,但在Chrome19中,由于页面正在滚动和恢复,会出现闪烁。我假设这是由于滚动和触发滚动事件之间的延迟(滚动位置被恢复)。

Firefox在popstate触发之前滚动页面(并触发scroll事件),Chrome先触发popstate,然后滚动文档。

我见过的所有使用历史记录的网站API要么使用与上述类似的解决方案,要么在用户后退/前进时忽略滚动位置的变化(例如GitHub)。

是否可以防止文档在popstate事件上完全滚动?


共3个答案

匿名用户

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

(谷歌于2015年9月2日宣布)

浏览器支持:

Chrome:支持(自46起)

Firefox:支持(自46起)

IE:不支持

边缘:支持(自79起)

Opera:支持(自33起)

Safari:支持

有关详细信息,请参阅MDN上的浏览器兼容性。

匿名用户

这已经是mozilla开发者核心一年多来的一个报告问题了。不幸的是,这张票并没有真正的进展。我认为Chrome是一样的:没有可靠的方法可以通过js解决滚动位置onpopstate,因为它是本地浏览器行为。

但是,如果您查看HTML5历史规范,它明确希望在状态对象上表示滚动位置,那么未来是有希望的:

历史对象将其浏览上下文的会话历史记录表示为会话历史记录条目的平面列表。每个会话历史记录条目由一个URL和可选的状态对象组成,此外还可能具有标题、文档对象、表单数据、滚动位置和与其关联的其他信息。

这一点,如果你阅读了上面提到的mozilla票证的注释,会给出一些迹象,表明在不久的将来滚动位置可能不会再恢复onpopstate,至少对于使用pushState的人来说是这样。

不幸的是,在此之前,滚动位置会在使用pushState时存储,而replace eState不会替换滚动位置。否则,这将相当容易,您可以使用replace eState在用户每次滚动页面时设置当前滚动位置(使用一些谨慎的onscroll处理程序)。

同样不幸的是,HTML5规范没有具体说明什么时候必须触发popstate事件,它只是说:“在某些情况下,当导航到会话历史记录条目时触发”,这并没有明确说明它是在之前还是之后;如果它总是在之前,处理popstate之后发生的滚动事件的解决方案是可能的。

取消滚动事件?

此外,如果滚动事件是可取消的,这也很容易。如果是,你可以取消一系列的第一个滚动事件(用户滚动事件就像旅鼠,它们有几十个,而历史重新定位触发的滚动事件是一个),你会没事的。

暂时没有解决办法

据我所知,我现在唯一建议的是等待HTML5规范完全实现,并在这种情况下随着浏览器的行为滚动,这意味着:当浏览器允许您滚动时,动画滚动,当有历史事件时,让浏览器重新定位页面。您唯一可以影响位置的是,当页面以良好的方式定位时,您可以使用pushState。任何其他解决方案要么必然会有错误,要么过于特定于浏览器,或者两者兼而有之。

匿名用户

你将不得不在这里使用某种可怕的浏览器嗅探。对于Firefox,我会使用你的解决方案来存储滚动位置并恢复它。

根据您的描述,我以为我有一个很好的Webkit解决方案,但我刚刚在Chrome21中尝试过,似乎Chrome先滚动,然后触发popstate事件,然后触发scroll事件。但作为参考,我想到了以下内容:

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

通过移动绝对定位的元素来假装页面正在滚动的黑魔法也被屏幕重绘速度排除在外。

所以我99%肯定答案是你不能,你将不得不使用你在问题中提到的妥协之一。两种浏览器都在JavaScript知道任何事情之前滚动,所以JavaScript只能在事件发生后做出反应。唯一的区别是Firefox直到Javascript触发后才会绘制屏幕,这就是为什么Firefox中有一个可行的解决方案,而WebKit中没有。