防止浏览器在HTML5 History popstate上滚动

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

我们的网站使用jQueryanimation滚动和History.js,状态变化应该通过pushstate或者popstate滚动用户到页面的不同区域。 麻烦的是浏览器在popstate事件发生时自动恢复前一状态的滚动位置。

我试过使用容器元素设置为文档的100%宽度和高度,并滚动该容器内的内容。 我发现的问题似乎不像滚动文档那么顺利, 特别是如果使用大量的css3像盒子阴影和渐变。

我也尝试在用户启动的滚动过程中存储文档的滚动位置,并在浏览器滚动页面(在popup窗口)后恢复它。 这在Firefox 12中工作正常,但在Chrome 19中,由于页面正在滚动和恢复,因此闪烁。 我认为这是在滚动和滚动事件被触发(滚动位置被恢复)之间的延迟。

Firefox在popstate触发前滚动页面(并触发滚动事件),Chrome首先popup窗口,然后滚动文档。

我所见过的所有使用历史loggingAPI的站点都使用类似于上述的解决scheme,或者在用户前进/后退(例如GitHub)时忽略滚动位置更改。

是否有可能防止文件正在滚动popstate事件?

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

( 由Google于2015年9月2日宣布)

浏览器支持:

Chrome:支持(自46以来)

Firefox:将于2016年4月19日起支持(版本46.0)

IE / Edge: 请求支持 (然后在评论中留下链接)

歌剧:支持(自33)

更多关于MDN的信息。

这已经是mozilla开发者核心已经有一年多的报道问题了 。 不幸的是,机票并没有真正的进步。 我认为Chrome是相同的:因为它是原生浏览器的行为,所以没有可靠的方法来通过js来处理onpopstate的滚动位置。

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

历史对象将其浏览上下文的会话历史logging表示为会话历史logging条目的平面列表。 每个会话历史logging条目由URL和可选的状态对象组成,并且还可以具有标题,Document对象,表单数据,滚动位置以及与其相关联的其他信息。

这个,如果你阅读了上面提到的mozilla ticket的评论,就会发现在不久的将来滚动位置将不会在onpopstate被恢复,至less对于使用pushState

不幸的是,在那之前,当使用pushState时,滚动位置被存储,并且replaceState不能代替滚动位置。 否则,这将是相当容易的,你可以使用replaceState来设置当前的滚动位置,每当用户滚动页面(有一些谨慎onscroll处理程序)。

另外不幸的是,HTML5规范并没有指定popstate事件到底什么时候被触发,它只是说:“在某些情况下,在导航到会话历史logging的时候会被解雇”,而不是清楚地说出是在之前还是之后。 如果以前总是这样,那么可以在popstate之后处理滚动事件。

取消滚动事件?

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

目前没有解决办法

据我所知,现在我唯一要推荐的就是等待HTML5规范完全实现,并在浏览器允许的情况下使浏览器行为滚动,这意味着:在浏览器允许的时候animation滚动,当有历史事件时,让浏览器重新定位页面。 唯一可以影响位置的方法就是在页面定位好的时候使用pushState返回。 任何其他解决scheme都必然有错误,或者太过于浏览器特定,或两者兼而有之。

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

我以为我有一个很好的Webkit解决scheme根据您的描述,但我只是尝试在Chrome 21中,似乎Chrome浏览器先滚动,然后触发popstate事件,然后触发滚动事件。 但是作为参考,这是我想出来的:

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

黑魔法比如假装页面是通过移动一个absolute定位的元素来滚动也是由屏幕重绘的速度排除的。

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

现在你可以做了

 history.scrollRestoration = 'manual'; 

这应该防止浏览器滚动。 这只能在Chrome 46及以上版本中运行,但似乎Firefox也计划支持它

解决方法是使用position: fixed并指定top等于页面的滚动位置。

这里是一个例子:

 $(window).on('popstate', function() { $('.yourWrapAroundAllContent').css({ position: 'fixed', top: -window.scrollY }); requestAnimationFrame(function() { $('.yourWrapAroundAllContent').css({ position: 'static', top: 0 }); }); }); 

是的,你反而收到闪烁的滚动条,但它不那么邪恶。

以下修补程序应适用于所有浏览器。

您可以在卸载事件中将滚动位置设置为0。 你可以在这里阅读这个活动: https : //developer.mozilla.org/en-US/docs/Web/Events/unload 。 本质上,卸载事件在离开页面之前就会触发。

通过在卸载时将scrollPosition设置为0意味着当您使用set PushState离开页面时,它将scrollPosition设置为0.当您通过刷新或按回来返回到此页面时,它将不会自动滚动。

 //Listen for unload event. This is triggered when leaving the page. //Reference: https://developer.mozilla.org/en-US/docs/Web/Events/unload window.addEventListener('unload', function(e) { //set scroll position to the top of the page. window.scrollTo(0, 0); }); 

将scrollRestoration设置为手动不起作用,这里是我的解决scheme。

 window.addEventListener('popstate', function(e) { var scrollTop = document.body.scrollTop; window.addEventListener('scroll', function(e) { document.body.scrollTop = scrollTop; }); }); 

在页面顶部的某个位置创build一个“span”元素,并在加载时将焦点设置为此。 浏览器将滚动到焦点元素。 我知道这是一个解决方法,重点放在“span”不适用于所有浏览器(uhmmm,Safari)。 希望这可以帮助。

这是我在一个网站上实现的,当poststate被激发时(后退button),滚动位置需要关注到特定元素:

 $(document).ready(function () { if (window.history.pushState) { //if push supported - push current page onto stack: window.history.pushState(null, document.title, document.location.href); } //add handler: $(window).on('popstate', PopStateHandler); } //fires when the back button is pressed: function PopStateHandler(e) { emnt= $('#elementID'); window.scrollTo(0, emnt.position().top); alert('scrolling to position'); } 

经过testing,并在Firefox中工作。

Chrome将滚动到位置,但重新定位到原来的位置。

像其他人说的那样,没有办法做到这一点,只有丑陋的方式。 删除滚动事件监听器不适合我,所以这是我的丑陋的方式来做到这一点:

 /// GLOBAL VARS //////////////////////// var saveScrollPos = false; var scrollPosSaved = window.pageYOffset; //////////////////////////////////////// jQuery(document).ready(function($){ //// Go back with keyboard shortcuts //// $(window).keydown(function(e){ var key = e.keyCode || e.charCode; if((e.altKey && key == 37) || (e.altKey && key == 39) || key == 8) saveScrollPos = false; }); ///////////////////////////////////////// //// Go back with back button //// $("html").bind("mouseout", function(){ saveScrollPos = false; }); ////////////////////////////////// $("html").bind("mousemove", function(){ saveScrollPos = true; }); $(window).scroll(function(){ if(saveScrollPos) scrollPosSaved = window.pageYOffset; else window.scrollTo(0, scrollPosSaved); }); }); 

它在Chrome,FF和IE中工作(它首次在IE中闪回)。 欢迎任何改进build议! 希望这可以帮助。

可能想尝试这个?

 window.onpopstate = function(event) { return false; };