【问题标题】:Popstate on page's load in Chrome在 Chrome 中页面加载时出现 Popstate
【发布时间】:2011-09-19 06:57:10
【问题描述】:

我正在为我的网络应用程序使用 History API,但遇到了一个问题。 我执行 Ajax 调用来更新页面上的一些结果并使用history.pushState() 来更新浏览器的位置栏,而无需重新加载页面。然后,当然,我使用window.popstate 以便在单击后退按钮时恢复之前的状态。

这个问题是众所周知的——Chrome 和 Firefox 对待 popstate 事件的方式不同。虽然 Firefox 不会在第一次加载时启动它,但 Chrome 会。我希望拥有 Firefox 风格,并且不会在加载时触发事件,因为它只是在加载时使用完全相同的结果更新结果。除了使用History.js,还有其他解决方法吗?我不想使用它的原因是——它本身需要太多的 JS 库,而且由于我需要在已经有太多 JS 的 CMS 中实现它,所以我想最小化我放入的 JS .

所以,想知道是否有办法让 Chrome 在加载时不启动 popstate,或者,也许有人试图使用 History.js,因为所有库都被混合到一个文件中。

【问题讨论】:

  • 补充一点,Firefox 的行为是指定的和正确的行为。从 Chrome 34 开始,它现已修复!为 Opera 开发者喝彩!

标签: javascript html google-chrome dom-events browser-history


【解决方案1】:

您可以创建一个事件并在您的 onload 处理程序之后触发它。

var evt = document.createEvent("PopStateEvent");
evt.initPopStateEvent("popstate", false, false, { .. state object  ..});
window.dispatchEvent(evt);

请注意,这在 Chrome/Safari 中略有损坏,但我已将补丁提交到 WebKit,它应该很快就会可用,但这是“最正确”的方式。

【讨论】:

    【解决方案2】:

    解决方案已在jquery.pjax.js 195-225 行找到:

    // Used to detect initial (useless) popstate.
    // If history.state exists, assume browser isn't going to fire initial popstate.
    var popped = ('state' in window.history), initialURL = location.href
    
    
    // popstate handler takes care of the back and forward buttons
    //
    // You probably shouldn't use pjax on pages with other pushState
    // stuff yet.
    $(window).bind('popstate', function(event){
      // Ignore inital popstate that some browsers fire on page load
      var initialPop = !popped && location.href == initialURL
      popped = true
      if ( initialPop ) return
    
      var state = event.state
    
      if ( state && state.pjax ) {
        var container = state.pjax
        if ( $(container+'').length )
          $.pjax({
            url: state.url || location.href,
            fragment: state.fragment,
            container: container,
            push: false,
            timeout: state.timeout
          })
        else
          window.location = location.href
      }
    })
    

    【讨论】:

    • 此解决方案在 Windows (Chrome 19) 上停止为我工作,但在 Mac (Chrome 18) 上仍然有效。似乎 history.state 存在于 Chrome 19 但不是 18。
    • @johnnymire 我发布了针对 Chrome 19 更低版本的解决方案:stackoverflow.com/a/10651028/291500
    • 有人应该编辑这个答案以反映这个后 Chrome 19 的行为。
    • 对于任何寻求解决方案的人来说,Torben 的回答对目前的 Chrome 和 Firefox 版本来说就像是一种魅力
    • 现在在 2015 年 4 月,我使用此解决方案解决了 Safari 的问题。我必须使用 onpopstate 事件来实现在使用 Cordova 的混合 iOS/Android 应用程序上按下后退按钮的处理。即使在我以编程方式调用的重新加载之后,Safari 也会触发其 onpopstate。按照我的代码设置方式,在调用 reload() 后它进入了无限循环。这解决了它。我只需要事件声明后的前 3 行(加上顶部的分配)。
    【解决方案3】:

    比重新实现 pjax 更直接的解决方案是在 pushState 上设置一个变量,并检查 popState 上的变量,因此初始 popState 不会在加载时不一致地触发(不是特定于 jquery 的解决方案,只是将它用于事件) :

    $(window).bind('popstate', function (ev){
      if (!window.history.ready && !ev.originalEvent.state)
        return; // workaround for popstate on load
    });
    
    // ... later ...
    
    function doNavigation(nextPageId) {
      window.history.ready = true;
    
      history.pushState(state, null, 'content.php?id='+ nextPageId); 
      // ajax in content instead of loading server-side
    }
    

    【讨论】:

    • 如果总是评估为假,那不是吗?我的意思是,window.history.ready 总是正确的。
    • 更新了示例,使其更加清晰。基本上,您只想在完全清除后才处理 popstate 事件。您可以在加载后 500 毫秒使用 setTimeout 达到相同的效果,但老实说它有点便宜。
    • 这应该是 IMO 的答案,我尝试了 Pavels 解决方案,但它在 Firefox 中无法正常工作
    • 这对我来说是解决这个烦人问题的简单方法。非常感谢!
    • !window.history.ready && !ev.originalEvent.state -- 当我刷新页面时,这两件事是从不定义的。所以因此没有代码执行。
    【解决方案4】:

    这是我的解决方法。

    window.setTimeout(function() {
      window.addEventListener('popstate', function() {
        // ...
      });
    }, 1000);
    

    【讨论】:

    • 在 Ubuntu 上的 Chromium 28 上不适合我。有时,该事件仍会被触发。
    • 我自己试过了,并不总是有效。有时,popstate 会在超时之后触发,具体取决于初始页面加载
    【解决方案5】:

    Webkit 的初始 onpopstate 事件没有分配状态,因此您可以使用它来检查不需要的行为:

    window.onpopstate = function(e){
        if(e.state)
            //do something
    };
    

    一个全面的解决方案,允许导航回原始页面,将建立在这个想法之上:

    <body onload="init()">
        <a href="page1" onclick="doClick(this); return false;">page 1</a>
        <a href="page2" onclick="doClick(this); return false;">page 2</a>
        <div id="content"></div>
    </body>
    
    <script>
    function init(){
       openURL(window.location.href);
    }
    function doClick(e){
        if(window.history.pushState)
            openURL(e.getAttribute('href'), true);
        else
            window.open(e.getAttribute('href'), '_self');
    }
    function openURL(href, push){
        document.getElementById('content').innerHTML = href + ': ' + (push ? 'user' : 'browser'); 
        if(window.history.pushState){
            if(push)
                window.history.pushState({href: href}, 'your page title', href);
            else
                window.history.replaceState({href: href}, 'your page title', href);
        }
    }
    window.onpopstate = function(e){
        if(e.state)
            openURL(e.state.href);
    };
    </script>
    

    虽然这个可能仍然会触发两次(带有一些漂亮的导航),但可以通过检查之前的 href 来简单地处理它。

    【讨论】:

    • 谢谢。 pjax 解决方案在 iOS 5.0 上停止工作,因为 window.history.state 在 iOS 中也为空。只需检查 e.state 属性是否为 null 就足够了。
    • 此解决方案是否存在任何已知问题?它在我测试的任何浏览器上都能完美运行。
    【解决方案6】:

    在版本 19 的 Google Chrome 中,@spliter 的解决方案停止工作。正如@johnnymire 指出的那样,Chrome 19 中的 history.state 存在,但它是空的。

    我的解决方法是将 window.history.state !== null 添加到检查 window.history 中是否存在状态:

    var popped = ('state' in window.history && window.history.state !== null), initialURL = location.href;
    

    我在所有主流浏览器以及 Chrome 版本 19 和 18 中都对其进行了测试。看起来它可以工作。

    【讨论】:

    • 感谢您的提醒;我认为您可以仅使用 != null 简化 innull 检查,因为这也将处理 undefined
    • 只有当你总是在你的history.pushState() 方法中使用null,比如history.pushState(null,null,'http//example.com/) 时,才能使用它。当然,这可能是大多数用法(这就是它在 jquery.pjax.js 和大多数其他演示中的设置方式)。但是如果浏览器实现了window.history.state(如FF和Chrome 19+),window.history.state could be non-null on a refresh or after a browser restart
    • 这在 Chrome 21 中不再适用于我。我最后只是在页面加载时将 popped 设置为 false,然后在任何推送或弹出时将 popped 设置为 true。这会在第一次加载时中和 Chrome 的流行音乐。这里解释得更好:github.com/defunkt/jquery-pjax/issues/143#issuecomment-6194330
    • @ChadvonNau 好主意,而且效果很好 - 非常感谢!
    • 我遇到了同样的问题,最后使用了简单的@ChadvonNau 解决方案。
    【解决方案7】:

    仅使用 setTimeout 不是正确的解决方案,因为您不知道加载内容需要多长时间,因此可能会在超时后发出 popstate 事件。

    这是我的解决方案: https://gist.github.com/3551566

    /*
    * Necessary hack because WebKit fires a popstate event on document load
    * https://code.google.com/p/chromium/issues/detail?id=63040
    * https://bugs.webkit.org/process_bug.cgi
    */
    window.addEventListener('load', function() {
      setTimeout(function() {
        window.addEventListener('popstate', function() {
          ...
        });
      }, 0);
    });
    

    【讨论】:

    • 这是唯一对我有用的解决方案。谢谢!
    • 甜蜜!比最受好评和/或接受的答案更好!
    • 有没有人有这个解决方案的活动链接,我无法访问给定的链接gist.github.com/3551566
    • 为什么需要要点?
    • 完美答案。谢谢。
    【解决方案8】:

    这是我的解决方案:

    var _firstload = true;
    $(function(){
        window.onpopstate = function(event){
            var state = event.state;
    
            if(_firstload && !state){ 
                _firstload = false; 
            }
            else if(state){
                _firstload = false;
                // you should pass state.some_data to another function here
                alert('state was changed! back/forward button was pressed!');
            }
            else{
                _firstload = false;
                // you should inform some function that the original state returned
                alert('you returned back to the original state (the home state)');
            }
        }
    })   
    

    【讨论】:

      【解决方案9】:

      提出的解决方案在页面重新加载时存在问题。以下似乎效果更好,但我只测试了 Firefox 和 Chrome。它使用实际情况,e.event.statewindow.history.state 之间似乎存在差异。

      window.addEvent('popstate', function(e) {
          if(e.event.state) {
              window.location.reload(); // Event code
          }
      });
      

      【讨论】:

      • e.event 未定义
      【解决方案10】:

      如果您不想对添加到 onpopstate 的每个处理程序采取特殊措施,我的解决方案可能对您来说很有趣。此解决方案的一大优点是可以在页面加载完成之前处理 onpopstate 事件。

      只需在添加任何 onpopstate 处理程序之前运行此代码一次,一切都会按预期运行(又名 Mozilla ^^)

      (function() {
          // There's nothing to do for older browsers ;)
          if (!window.addEventListener)
              return;
          var blockPopstateEvent = document.readyState!="complete";
          window.addEventListener("load", function() {
              // The timeout ensures that popstate-events will be unblocked right
              // after the load event occured, but not in the same event-loop cycle.
              setTimeout(function(){ blockPopstateEvent = false; }, 0);
          }, false);
          window.addEventListener("popstate", function(evt) {
              if (blockPopstateEvent && document.readyState=="complete") {
                  evt.preventDefault();
                  evt.stopImmediatePropagation();
              }
          }, false);
      })();
      

      工作原理:

      Chrome、Safari 和可能的其他 webkit 浏览器在文档加载时触发 onpopstate 事件。这不是故意的,所以我们阻止 popstate 事件,直到文档加载后的第一个事件循环循环。这是通过 preventDefault 和 stopImmediatePropagation 调用完成的(与 stopPropagation 不同,stopImmediatePropagation 会立即停止所有事件处理程序调用)。

      但是,由于当 Chrome 错误地触发 onpopstate 时文档的 readyState 已经处于“完成”状态,所以我们允许在文档加载完成之前触发的 opopstate 事件以允许在文档加载之前调用 onpopstate。

      2014 年 4 月 23 日更新: 修复了在页面加载后执行脚本时会阻止 popstate 事件的错误。

      【讨论】:

      • 迄今为止对我来说最好的解决方案。多年来我一直在使用 setTimeout 方法,但是当页面加载缓慢/用户非常快速地触发 pushstate 时,它​​会导致错误行为。如果设置了状态,window.history.state 会在重新加载时失败。在 pushState 混乱代码之前设置变量。您的解决方案很优雅,可以放在其他脚本之上,而不会影响代码库的其余部分。谢谢。
      • 这就是解决方案! 如果我几天前在尝试解决这个问题时读到了这篇文章就好了。这是唯一一个完美运行的。谢谢!
      • 优秀的解释和唯一值得一试的解决方案。
      • 这是一个很好的解决方案。在撰写本文时,最新的 chrome 和 FF 很好,问题在于 safari(mac 和 iphone)。这个解决方案就像一个魅力。
      • 哇!如果 Safari 毁了你的一天,这些就是你要找的代码!
      【解决方案11】:

      让 Chrome 在页面加载时不触发 popstate 的最佳方法是投票 https://code.google.com/p/chromium/issues/detail?id=63040。他们已经知道 Chrome 不符合 HTML5 规范已经整整两年了,仍然没有修复它!

      【讨论】:

      • 自 Chrome 34 以来已修复此问题。
      【解决方案12】:

      我知道你反对它,但你真的应该只使用 History.js,因为它可以清除一百万个浏览器不兼容问题。我去了手动修复路线,后来发现有越来越多的问题,你只能在路上找到。现在真的没那么难:

      <script src="//cdnjs.cloudflare.com/ajax/libs/history.js/1.8/native.history.min.js" type="text/javascript"></script>
      

      然后阅读https://github.com/browserstate/history.js的api

      【讨论】:

        【解决方案13】:

        这在 Firefox 和 Chrome 中对我有用

        window.onpopstate = function(event) { //back button click
            console.log("onpopstate");
            if (event.state) {
                window.location.reload();
            }
        };
        

        【讨论】:

          【解决方案14】:

          这解决了我的问题。我所做的只是设置了一个超时函数,该函数将函数的执行延迟到足以错过在页面加载时触发的 popstate 事件

          if (history && history.pushState) {
            setTimeout(function(){
              $(window).bind("popstate", function() {
                $.getScript(location.href);
              });
            },3000);
          }
          

          【讨论】:

            【解决方案15】:

            在使用event.state !== null 的情况下,在历史记录中返回到第一个加载的页面将无法在非移动浏览器中工作。 我使用 sessionStorage 来标记 ajax 导航真正开始的时间。

            history.pushState(url, null, url);
            sessionStorage.ajNavStarted = true;
            
            window.addEventListener('popstate', function(e) {
                if (sessionStorage.ajNavStarted) {
                    location.href = (e.state === null) ? location.href : e.state;
                }
            }, false);

            【讨论】:

            • window.onload = function() { if (history.state === null) { history.replaceState(location.pathname + location.search + location.hash, null, location.pathname + location .search + location.hash); } }; window.addEventListener('popstate', function(e) { if (e.state !== null) { location.href = e.state; } }, false);
            猜你喜欢
            • 2018-09-26
            • 2013-07-06
            • 1970-01-01
            • 2019-06-25
            • 1970-01-01
            • 2013-06-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多