【问题标题】:Prevent browser scroll on HTML5 History popstate防止浏览器在 HTML5 History popstate 上滚动
【发布时间】:2012-05-31 07:52:40
【问题描述】:

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

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

我尝试使用设置为文档宽度和高度 100% 的容器元素并滚动该容器内的内容。我发现的问题是它似乎不像滚动文档那么流畅。尤其是在使用大量 css3(如 box-shadows 和渐变)时。

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

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

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

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

【问题讨论】:

  • 在设置滚动位置后,您可能会成功读取元素尺寸(以强制页面重排)。例如document.body.clientHeight 但我还没有看到它适用于所有浏览器。
  • 如果你操纵了文档的滚动功能,我至少会从这里开始。这里有一个解决方案:stackoverflow.com/questions/7035331/…

标签: javascript html dom-events browser-history


【解决方案1】:
if ('scrollRestoration' in history) {
  history.scrollRestoration = 'manual';
}

Announced by Google 2015 年 9 月 2 日)

浏览器支持:

Chrome:支持(46 起)

Firefox:支持(从 46 开始)

IE:不支持

Edge:支持(79 起)

Opera:支持(33 起)

Safari:支持

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

【讨论】:

  • 更短的:history.scrollRestoration && history.scrollRestoration = 'manual'; 甚至是var sr = 'scrollRestoration'; history[sr] && history[sr] = 'manual';
【解决方案2】:

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

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

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

如果您阅读了上面提到的 mozilla 票证上的 cmets,这表明在不久的将来滚动位置可能不会再恢复 onpopstate,至少对于使用 pushState 的人来说是这样。

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

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

取消滚动事件?

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

暂时没有解决办法

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

【讨论】:

  • 历史规范的措辞现在略有不同。此外,如果您向下滚动并查看 pushState 的文档,则没有提及添加滚动位置。规范建议会话历史条目可能包含保存的滚动位置,但我不确定这是否意味着 Web 开发人员能够设置它。
【解决方案3】:

您将不得不在这里使用某种可怕的浏览器嗅探。对于 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 中没有。

【讨论】:

    【解决方案4】:

    现在你可以做

    history.scrollRestoration = 'manual';
    

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

    【讨论】:

      【解决方案5】:

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

      这是一个例子:

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

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

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

          【解决方案7】:

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

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

          【讨论】:

          • 传闻 document.body.scrollTop 已被弃用。这对我有用:document.documentElement.scrollTop.
          【解决方案8】:

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

          【讨论】:

            【解决方案9】:

            这是我在一个网站上实现的,它希望在触发 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 会滚动到某个位置,但随后会重新定位到原来的位置。

            【讨论】:

              【解决方案10】:

              就像其他人说的那样,没有真正的方法可以做到,只有丑陋的 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 时它会闪烁)。欢迎任何改进建议!希望这会有所帮助。

              【讨论】:

                【解决方案11】:

                可能想试试这个?

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

                【讨论】:

                • 这不起作用。有些事件是无法阻止的,popstate 就是其中之一。
                猜你喜欢
                • 1970-01-01
                • 2012-09-11
                • 1970-01-01
                • 1970-01-01
                • 2011-10-25
                • 2011-06-04
                • 1970-01-01
                • 1970-01-01
                • 2017-11-01
                相关资源
                最近更新 更多