在 Chrome 中加载页面时的 Popstate

IT技术 javascript html google-chrome dom-events browser-history
2021-02-21 12:07:54

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

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

所以,想知道是否有办法让 Chromepopstate在加载时不启动,或者,也许有人尝试使用 History.js,因为所有库都混搭成一个文件。

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 版中对其进行了测试。看起来它可以工作。

这对我来说在 Chrome 21 中不再起作用了。我只是在页面加载时将 popped 设置为 false,然后在任何推送或弹出时将 popped 设置为 true。这在第一次加载时中性了 Chrome 的流行音乐。这里解释得更好一些:github.com/defunkt/jquery-pjax/issues/143#issuecomment-6194330
2021-05-03 12:07:54
我遇到了同样的问题,并最终使用了简单的 @ChadvonNau 解决方案。
2021-05-06 12:07:54
感谢您的提醒;我认为您可以简化innull检查,!= null因为这也可以解决undefined
2021-05-13 12:07:54
仅当您始终nullhistory.pushState()history.pushState(null,null,'http//example.com/). 当然,这可能是大多数用法(这就是它在 jquery.pjax.js 和大多数其他演示中的设置方式)。但如果浏览器实现window.history.state(如 FF 和 Chrome 19+),window.history.state 在刷新或浏览器重启后可能为非空
2021-05-15 12:07:54
@ChadvonNau 好主意,它很有用 - 非常感谢!
2021-05-19 12:07:54

如果您不想为添加到 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 事件被阻止的错误。

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

仅使用 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);
});
甜的!比最受好评和/或接受的答案更有效!
2021-04-21 12:07:54
有没有人有这个解决方案的活动链接,我无法访问给定的链接gist.github.com/3551566
2021-04-22 12:07:54
这是唯一对我有用的解决方案。谢谢!
2021-05-03 12:07:54
完美的答案。谢谢。
2021-05-04 12:07:54
你为什么需要要点?
2021-05-19 12:07:54

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
  }
})
@johnnymire 我发布了我的 Chrome 19 解决方案:stackoverflow.com/a/10651028/291500
2021-05-05 12:07:54
该解决方案在 Windows (Chrome 19) 上对我不起作用,但在 Mac (Chrome 18) 上仍然有效。似乎 history.state 存在于 Chrome 19 但不存在于 18 中。
2021-05-07 12:07:54
对于任何正在寻找解决方案的人来说,Torben 的答案在今天的当前 Chrome 和 Firefox 版本上就像一个魅力
2021-05-07 12:07:54
有人应该编辑此答案以反映此 Chrome 19 后的行为。
2021-05-09 12:07:54
现在在 2015 年 4 月,我使用这个解决方案来解决 Safari 的一个问题。我必须使用 onpopstate 事件才能在使用 Cordova 的混合 iOS/Android 应用程序上实现按下后退按钮的处理。即使在我以编程方式调用的重新加载后,Safari 也会触发其 onpopstate。按照我的代码设置方式,在调用 reload() 后它进入了无限循环。这修复了它。我只需要事件声明后的前 3 行(加上顶部的赋值)。
2021-05-17 12:07:54

比重新实现 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 && !ev.originalEvent.state-当我刷新页面时,这两件事从未被定义。所以因此没有代码执行。
2021-04-21 12:07:54
如果总是评估为假,那不是吗?我的意思是,window.history.ready永远是真的。
2021-04-30 12:07:54
这应该是 IMO 的答案,我尝试了 Pavels 解决方案,但它在 Firefox 中无法正常工作
2021-05-09 12:07:54
这对我来说是解决这个烦人问题的简单方法。非常感谢!
2021-05-12 12:07:54
更新了示例,使其更加清晰。基本上,您只想在完全清除后处理 popstate 事件。您可以在加载后 500 毫秒使用 setTimeout 实现相同的效果,但老实说它有点便宜。
2021-05-20 12:07:54