Html Chrome 中页面加载时的 Popstate

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6421769/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-29 09:11:31  来源:igfitidea点击:

Popstate on page's load in Chrome

htmlgoogle-chromejavascript-eventsbrowser-history

提问by spliter

I am using History API for my web app and have one issue. I do Ajax calls to update some results on the page and use history.pushState()in order to update the browser's location bar without page reload. Then, of course, I use window.popstatein order to restore previous state when back-button is clicked.

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

The problem is well-known — Chrome and Firefox treat that popstate event differently. While Firefox doesn't fire it up on the first load, Chrome does. I would like to have Firefox-style and not fire the event up on load since it just updates the results with exactly the same ones on load. Is there a workaround except using History.js? The reason I don't feel like using it is — it needs way too many JS libraries by itself and, since I need it to be implemented in a CMS with already too much JS, I would like to minimize JS I am putting in it.

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

So, would like to know whether there is a way to make Chrome not fire up 'popstate' on load or, maybe, somebody tried to use History.js as all libraries mashed up together into one file.

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

采纳答案by Pavel Linkesch

In Google Chrome in version 19 the solution from @spliter stopped working. As @johnnymire pointed out, history.state in Chrome 19 exists, but it's null.

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

My workaround is to add window.history.state !== nullinto checking if state exists in window.history:

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

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

I tested it in all major browsers and in Chrome versions 19 and 18. It looks like it works.

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

回答by Torben

In case you do not want to take special measures for each handler you add to onpopstate, my solution might be interesting for you. A big plus of this solution is also that onpopstate events can be handled before the page loading has been finished.

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

Just run this code once before you add any onpopstate handlers and everything should work as expected (aka like in Mozilla ^^).

只需在添加任何 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);
})();

How it works:

这个怎么运作:

Chrome, Safari and probably other webkit browsers fire the onpopstate event when the document has been loaded. This is not intended, so we block popstate events until the the first event loop cicle after document has been loaded. This is done by the preventDefault and stopImmediatePropagation calls (unlike stopPropagation stopImmediatePropagation stops all event handler calls instantly).

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

However, since the document's readyState is already on "complete" when Chrome fires onpopstate erroneously, we allow opopstate events, which have been fired before document loading has been finished to allow onpopstate calls before the document has been loaded.

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

Update 2014-04-23:Fixed a bug where popstate events have been blocked if the script is executed after the page has been loaded.

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

回答by Sonny Piers

Using setTimeout only isn't a correct solution because you have no idea how long it will take for the content to be loaded so it's possible the popstate event is emitted after the timeout.

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

Here is my solution: https://gist.github.com/3551566

这是我的解决方案: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);
});

回答by spliter

The solution has been found in jquery.pjax.jslines 195-225:

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

回答by Tom McKenzie

A more direct solution than reimplementing pjax is set a variable on pushState, and check for the variable on popState, so the initial popState doesn't inconsistently fire on load (not a jquery-specific solution, just using it for events):

比重新实现 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
}

回答by som

Webkit's initial onpopstateevent has no state assigned, so you can use this to check for the unwanted behaviour:

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

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

A comprehensive solution, allowing for navigation back to the original page, would build on this idea:

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

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

While this couldstill fire twice (with some nifty navigation), it can be handled simply with a check against the previous href.

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

回答by Baggz

This is my workaround.

这是我的解决方法。

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

回答by prograhammer

Here's my solution:

这是我的解决方案:

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

回答by ilusionist

In case of use event.state !== nullreturning back in history to first loaded page won't work in non mobile browsers. I use sessionStorage to mark when ajax navigation really starts.

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

回答by Dave

The best way to get Chrome to not fire popstate on a page load is to up-vote https://code.google.com/p/chromium/issues/detail?id=63040. They've known Chrome isn't in compliance with the HTML5 spec for two full years now and still haven't fixed it!

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