在Chrome中加载页面上的Popstate

时间:2011-06-21 07:25:41

标签: html5 google-chrome javascript-events browser-history

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

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

所以,想知道是否有办法让Chrome不会在加载时弹出“popstate”,或者有人试图使用History.js,因为所有库都拼凑成一个文件。

15 个答案:

答案 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.statewindow.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>

https://github.com/browserstate/history.js

阅读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();
    }
};