阻止浏览器滚动HTML5历史记录popstate

时间:2012-05-24 17:28:01

标签: javascript html5 javascript-events browser-history

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

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

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

我还尝试在用户启动的滚动期间存储文档的滚动位置,并在浏览器滚动页面后(在popstate上)恢复它。这在Firefox 12中运行良好,但在Chrome 19中,由于页面正在滚动和恢复,因此存在闪烁。我假设这与滚动和被触发的滚动事件之间的延迟有关(滚动位置被恢复的地方)。

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

我见过的所有使用历史记录API的网站都使用与上述类似的解决方案,或者只是在用户返回/转发时忽略滚动位置更改(例如GitHub)。

是否可以阻止文档在popstate事件中滚动?

11 个答案:

答案 0 :(得分:55)

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

(2015年9月2日Announced by Google

浏览器支持:

Chrome:支持(自46以来)

Firefox:支持(自46起)

IE / Edge:不支持,ask for support(然后在评论中保留链接)

Opera:支持(自33起)<​​/ p>

Safari:支持

有关详细信息,请参阅Browser compatibility on MDN

答案 1 :(得分:31)

这是reported issue with the mozilla developer core for more than a year now。不幸的是,机票并没有真正进步。我认为Chrome是相同的:没有可靠的方法通过js处理滚动位置onpopstate,因为它是本机浏览器行为。

如果你看一下明确希望在状态对象上表示滚动位置的HTML5 history spec,那么对未来充满希望:

  

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

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

不幸的是,在此之前,使用pushState时会存储滚动位置,replaceState不会替换滚动位置。否则,它会相当简单,并且每次用户滚动页面时都可以使用replaceState来设置当前的滚动位置(使用一些谨慎的onscroll处理程序)。

同样不幸的是,HTML5规范并未指定何时必须触发popstate事件,它只是说:«在导航到会话历史记录条目的某些情况下会被触发»,这没有明确说明如果是在之前或之后;如果它总是在之前,可以在popstate之后处理滚动事件的解决方案。

取消滚动事件?

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

现在没有解决方案

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

答案 2 :(得分:4)

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

我认为我有一个很好的Webkit解决方案,基于你的描述,但我只是尝试在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中有一个可行的解决方案但在WebKit中没有。

答案 3 :(得分:3)

现在你可以做到

history.scrollRestoration = 'manual';

这应该会阻止浏览器滚动。这仅适用于Chrome 46及更高版本,但似乎Firefox也计划支持它

答案 4 :(得分:2)

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

以下是一个例子:

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

   requestAnimationFrame(function()
   {
      $('.yourWrapAroundAllContent').css({
         position: 'static',
         top: 0
      });          
   });
});

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

答案 5 :(得分:1)

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

您可以在卸载事件中将滚动位置设置为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);
});

答案 6 :(得分:1)

将scrollRestoration设置为手动对我不起作用,这是我的解决方案。

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

答案 7 :(得分:0)

在页面顶部的某处创建一个'span'元素,并在加载时将焦点设置为此值。浏览器将滚动到焦点元素。我知道这是一种解决方法,关注'span'并不适用于所有浏览器(嗯...... Safari)。希望这会有所帮助。

答案 8 :(得分:0)

以下是我在网站上实现的内容,该网站希望在触发poststate时将滚动位置聚焦到特定元素(后退按钮):

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

经过测试并在firefox中运行。

Chrome会滚动到位置,然后重新定位回原来的位置。

答案 9 :(得分:0)

像其他人说的那样,没有真正的方法去做,只有丑陋的hacky方式。删除滚动事件监听器对我来说不起作用,所以这是我丑陋的方式:

/// 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时闪烁)。欢迎任何改进建议!希望这会有所帮助。

答案 10 :(得分:-5)

可能想尝试一下吗?

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