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


83

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

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

我尝试使用设置为文档宽度和高度100%的容器元素,并在该容器内滚动内容。我发现的问题是,它似乎不像滚动文档那样平滑。尤其是在使用大量css3(例如框阴影和渐变)的情况下。

我还尝试了在用户启动滚动期间存储文档的滚动位置,并在浏览器滚动页面后(在popstate上)恢复了它的位置。在Firefox 12中可以正常工作,但在Chrome 19中,由于正在滚动和还原页面,因此会出现闪烁。我认为这与滚动和被触发的滚动事件(恢复滚动位置)之间的延迟有关。

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

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

是否可以防止在popstate事件中根本滚动文档?


在设置滚动位置后,读取元素尺寸(强制页面重排)可能会取得一些成功。例如,document.body.clientHeight但我还没有看到它在所有浏览器中都能正常工作。
肖恩·霍根

如果您操纵了文档的滚动功能,那至少会是我应该开始的地方。这里有一个解决方案:stackoverflow.com/questions/7035331/…–
杰夫·伍德

Answers:


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

由Google在2015年9月2日宣布)

浏览器支持:

Chrome:受支持(自46开始)

Firefox:受支持(自46开始)

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

Opera:受支持(自33起)

Safari:受支持

有关更多信息,请参见MDN上的浏览器兼容性


1
更短:history.scrollRestoration && history.scrollRestoration = 'manual';甚至var sr = 'scrollRestoration'; history[sr] && history[sr] = 'manual';
-Bharata

31

已有一年多的时间报道了Mozilla开发者核心的问题。不幸的是,罚单并没有真正进展。我认为Chrome是一样的:没有可靠的方法可以onpopstate通过js处理滚动位置,因为它是本机浏览器的行为。

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

历史记录对象将其浏览上下文的会话历史记录表示为会话历史记录条目的平面列表。每个会话历史记录条目都包含一个URL和一个可选的状态对象,此外,还可以包含一个标题,一个Document对象,表单数据,滚动位置以及与之关联的其他信息。

这并且如果您阅读了上面提到的mozilla票证上的评论,则表明您可能在不久的将来不再恢复滚动位置onpopstate,至少对于使用的用户而言pushState

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

同样不幸的是,HTML5规范没有指定确切的popstate事件必须在何时触发,它只是说:“在某些情况下,导航到会话历史记录条目时会触发”,它没有明确说明事件是在事件发生之前还是之后。如果始终在之前,则可能有一种解决滚动事件的方法,该事件发生在popstate之后。

取消滚动事件?

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

目前没有解决方案

据我所知,我目前唯一推荐的就是等待HTML5规范完全实现,并在这种情况下随浏览器行为滚动,这意味着:当浏览器允许您滚动时,使滚动动画,并在发生历史事件时让浏览器重新定位页面。您唯一可以影响位置的因素是您pushState以良好的定位方式返回页面时使用。任何其他解决方案都注定会有bug,或者太特定于浏览器,或者两者兼而有之。


历史记录规范的措词现在略有不同。另外,如果您向下滚动并查看pushState的文档,则没有提到添加滚动位置。该规范建议会话历史记录条目可能包含已保存的滚动位置,但是我不确定这意味着Web开发人员将能够设置它。
Walex 2014年

4

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

我认为根据您的描述我有一个很好的Webkit解决方案,但是我只是在Chrome 21中尝试过,看来Chrome首先滚动,然后触发popstate事件,然后触发scroll事件。但作为参考,这是我想出的:

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

现在你可以做

history.scrollRestoration = 'manual';

这应该可以防止浏览器滚动。目前,此功能仅在Chrome 46及更高版本中有效,但似乎Firefox也计划支持它。


2

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

这是一个例子:

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

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

是的,您反而会收到闪烁的滚动条,但是它的邪恶程度较小。


1

以下修复程序应在所有浏览器中都有效。

您可以在卸载事件上将滚动位置设置为0。您可以在这里阅读有关此事件的信息:https : //developer.mozilla.org/en-US/docs/Web/Events/unload。本质上,卸载事件在离开页面之前立即触发。

通过在卸载时将scrollPosition设置为0,意味着当您使用设置的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);
});

1

将scrollRestoration设置为手动不适用于我,这是我的解决方案。

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

0

在页面顶部的某个位置创建一个“ span”元素,并在加载时将焦点设置为此。浏览器将滚动到聚焦的元素。我知道这是一种解决方法,并非所有浏览器(uhmmm .. Safari)都无法解决“跨度”问题。希望这可以帮助。


0

这是我在一个站点上实现的,该站点希望在触发后状态(返回按钮)时将滚动位置聚焦到特定元素:

$(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浏览器将滚动到位置,然后重新定位到原始位置。


0

就像其他人所说的那样,没有真正的方法,只有丑陋的方法。删除滚动事件侦听器对我不起作用,因此这是我执行此操作的丑陋方式:

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


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.