Chrome中页面加载时的Popstate


94

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

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

因此,想知道是否有一种方法可以使Chrome在加载时不会启动“ popstate”,或者有人尝试使用History.js,因为所有库都合并到一个文件中。


3
另外,Firefox的行为是特定行为和正确行为。从Chrome 34开始,现在已修复!是的,歌剧开发者们!
亨利·陈

Answers:


49

在19版的Google Chrome浏览器中,@ spliter的解决方案停止了工作。正如@johnnymire指出的那样,Chrome 19中的history.state存在,但它为null。

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

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

我已经在所有主流浏览器以及Chrome 19和18版本中对其进行了测试。


5
感谢您的注意;我认为您可以简化in并进行null检查,!= null因为那样也可以解决undefined
pimvdb

5
使用这只会工作,如果你总是用null你的history.pushState()方法一样history.pushState(null,null,'http//example.com/)。当然,这可能是大多数用法(这就是在jquery.pjax.js和大多数其他演示中设置的方式)。但是,如果浏览器实现了window.history.state(例如FF和Chrome 19+),则刷新或在重新启动浏览器后
无法解释

19
在Chrome 21中,这对我来说不再起作用。我最后只是将页面加载时设置pop设置为false,然后在任何推送或弹出窗口中将pop设置为true。首次加载时,这使Chrome的弹出效果变得微不足道。它在这里解释得更好:github.com/defunkt/jquery-pjax/issues/143#issuecomment-6194330
Chad von Nau

1
@ChadvonNau这个好主意,而且很有效-非常感谢!
sowasred2012 2013年

我遇到了同样的问题,最终使用了简单的@ChadvonNau解决方案。
Pherrymason

30

如果您不想对添加到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事件。


4
到目前为止对我来说最好的解决方案。我已经使用setTimeout方法多年了,但是当页面加载缓慢/当用户非常快地触发pushstate时,它会导致错误行为。window.history.state如果设置了状态,则重新加载失败。在pushState之前设置变量会使代码混乱。您的解决方案很优雅,可以放在其他脚本之上,而不会影响其余的代码库。谢谢。
2014年

4
这就是解决方案!如果我几天前在尝试解决此问题时读了这么远的话。这是唯一运行良好的工具。谢谢!
Ben Fleming 2014年

1
出色的解释和唯一值得尝试的解决方案。
Sunny R Gupta

这是一个很好的解决方案。在撰写本文时,最新的chrome和FF罚款问题出现在safari(mac&iphone)中。该解决方案就像一个魅力。
Vin

1
!如果Safari破坏了您的一天,那么这些就是您想要的代码!
DanO

28

仅使用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);
});

这是唯一对我有用的解决方案。谢谢!
贾斯汀

3
甜!比最受好评和/或接受的答案更好!
ProblemsSuSumit

没有人有此解决方案的有效链接,我无法访问给定的链接gist.github.com/3551566
Harbir 2014年

1
为什么需要要点?
Oleg Vaskevich 2014年

完美的答案。谢谢。
sideroxylon 2015年

25

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
  }
})

8
该解决方案在Windows(Chrome 19)上对我来说不再起作用,而在Mac(Chrome 18)上仍然对我起作用。好像history.state存在于铬19而不是18,
johnnymire

1
@johnnymire我在较低的Chrome 19上发布了我的解决方案:stackoverflow.com/a/10651028/291500
Pavel Linkesch 2012年

有人应该编辑此答案以反映Chrome 19后的行为。
豪尔赫·苏亚雷斯·德里斯,2013年

5
对于任何人寻找一个解决方案,从托本答案就像在今天的目前的Chrome和Firefox版本的魅力
豪尔赫·苏亚雷斯代利斯

1
现在在2015年4月,我使用此解决方案解决了Safari的问题。我必须使用onpopstate事件来实现使用Cordova在混合iOS / Android应用程序上按后退按钮的处理。Safari即使在重新加载后仍会触发其onpopstate,我以编程方式对其进行了调用。通过我的代码设置方式,在调用reload()之后,它进入了无限循环。这样就解决了。我只需要事件声明后的前3行(加上顶部的分配)。
Martavis P.

14

与重新实现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总是如此。
Andriy Drozdyuk,2012年

更新了示例,使其更加清晰。基本上,您只希望在给出全部清除后才处理popstate事件。您可以在加载后使用500ms的setTimeout达到相同的效果,但是说实话,它有点便宜。
Tom McKenzie'2

3
这应该是IMO的答案,我尝试了Pavels解决方案,但在Firefox中无法正常工作
Porco

这对我来说是解决此烦人问题的简便方法。非常感谢!
nirazul

!window.history.ready && !ev.originalEvent.state- 当我刷新页面时,这两个东西从未定义。因此没有代码执行。
chovy

4

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来简单地处理它。


1
谢谢。pjax解决方案在iOS 5.0上停止工作,因为window.history.state在iOS中也是null。仅检查e.state属性是否为null就足够了。
赫斯基2012年

此解决方案是否存在任何已知问题?它可以在我测试的所有浏览器上完美运行。
Jacob Ewing 2014年

3

这是我的解决方法。

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

不适合我在Ubuntu上的Chromium 28上使用。有时,该事件仍然被触发。
豪尔赫·苏亚雷斯·德里斯,2013年

我自己尝试过此方法,但并不总是有效。有时popstate的触发时间要比超时时间晚,具体取决于初始页面加载
Andrew Burgess

1

这是我的解决方案:

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


0

如果使用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);
ilusionist '16

-1

提出的解决方案在页面重新加载上存在问题。以下似乎更好地工作,但我仅测试了Firefox和Chrome。它使用的现状,这似乎有之间的差异e.event.statewindow.history.state

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

e.event未定义
chovy

-1

我知道您提出了反对,但您实际上应该只使用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


-1

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

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

-2

您可以创建一个事件,然后在onload处理程序之后将其触发。

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

请注意,这在Chrome / Safari中有一点点中断,但是我已将该补丁提交到WebKit中,应该很快就可以使用,但这是“最正确”的方式。


-2

这在Firefox和Chrome中对我有用

window.onpopstate = function(event) { //back button click
    console.log("onpopstate");
    if (event.state) {
        window.location.reload();
    }
};
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.