我正在为我的网络应用使用历史记录API,但有一个问题。 我执行Ajax调用以更新页面上的一些结果并使用 history.pushState()来更新浏览器的位置栏而不重新加载页面。然后,当然,我使用 window.popstate ,以便在单击后退按钮时恢复以前的状态。
这个问题众所周知 - Chrome和Firefox以不同的方式对待该popstate事件。虽然Firefox没有在第一次加载时启动它,但Chrome确实如此。我想拥有Firefox风格并且不会在加载时触发事件,因为它只是在加载时使用完全相同的结果更新结果。除了使用History.js之外,是否有解决方法?我不想使用它的原因是 - 它本身需要太多的JS库,因为我需要在已经有太多JS的CMS中实现它,我想最小化JS我放入它
所以,想知道是否有办法让Chrome不会在加载时弹出“popstate”,或者有人试图使用History.js,因为所有库都拼凑成一个文件。
答案 0 :(得分:49)
在版本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中都进行了测试。它看起来很有效。
答案 1 :(得分: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事件,直到文档加载后的第一个事件循环cicle。这是通过preventDefault和stopImmediatePropagation调用完成的(与stopPropagation不同,stopImmediatePropagation会立即停止所有事件处理程序调用)。
但是,由于当Chrome错误地触发onopopate时,文档的readyState已经“完成”,我们允许opopstate事件,这些事件在文档加载完成之前被触发,以允许onpopstate调用在文档加载之前。
更新2014-04-23:修复了在页面加载后执行脚本时阻止popstate事件的错误。
答案 2 :(得分: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 :(得分: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
}
})
答案 4 :(得分: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
}
答案 5 :(得分: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就可以处理它。
答案 6 :(得分:3)
这是我的解决方法。
window.setTimeout(function() {
window.addEventListener('popstate', function() {
// ...
});
}, 1000);
答案 7 :(得分: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)');
}
}
})
答案 8 :(得分:0)
让Chrome在页面加载时不激活popstate的最佳方法是向上投票https://code.google.com/p/chromium/issues/detail?id=63040。他们已经知道Chrome已经整整两年不符合HTML5规范,但仍然没有修复它!
答案 9 :(得分:0)
如果使用reduce
在历史记录中返回第一个加载的页面将无法在非移动浏览器中使用。
我使用sessionStorage来标记ajax导航何时真正开始。
event.state !== null
答案 10 :(得分:-1)
所提供的解决方案在页面重新加载时存在问题。以下似乎效果更好,但我只测试了Firefox和Chrome。它使用了实际情况,e.event.state
和window.history.state
之间似乎存在差异。
window.addEvent('popstate', function(e) {
if(e.event.state) {
window.location.reload(); // Event code
}
});
答案 11 :(得分:-1)
我知道你反对它,但你应该真正使用History.js,因为它清除了一百万浏览器不兼容性。我去了手动修复路线后才发现有越来越多的问题你只能在路上找到。现在真的不是那么难:
<script src="//cdnjs.cloudflare.com/ajax/libs/history.js/1.8/native.history.min.js" type="text/javascript"></script>
阅读api
答案 12 :(得分:-1)
这解决了我的问题。我所做的只是设置了一个超时函数,它延迟了函数的执行时间,足以错过在pageload上触发的popstate事件
if (history && history.pushState) {
setTimeout(function(){
$(window).bind("popstate", function() {
$.getScript(location.href);
});
},3000);
}
答案 13 :(得分:-2)
您可以创建一个事件并在onload处理程序之后触发它。
var evt = document.createEvent("PopStateEvent");
evt.initPopStateEvent("popstate", false, false, { .. state object ..});
window.dispatchEvent(evt);
请注意,这在Chrome / Safari中有点突破,但我已将补丁提交到WebKit,它应尽快推出,但这是“最正确”的方式。
答案 14 :(得分:-2)
这在Firefox和Chrome中适用于我
window.onpopstate = function(event) { //back button click
console.log("onpopstate");
if (event.state) {
window.location.reload();
}
};