Chrome 中页面加载时的 Popstate

发布于 2024-11-16 18:39:34 字数 613 浏览 2 评论 0原文

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

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

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

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.popstate in order to restore previous state when back-button is clicked.

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.

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

流云如水 2024-11-23 18:39:34

在 Google Chrome 版本 19 中,@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 中测试了它。作品。

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.

My workaround is to add window.history.state !== null into checking if state exists in 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.

尘曦 2024-11-23 18:39:34

如果您不想对添加到 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 错误地触发 onpopstate 时,文档的 readyState 已经处于“完成”状态,因此我们允许在文档加载完成之前触发的 opopstate 事件,以允许在文档加载之前调用 onpopstate。

更新 2014-04-23: 修复了以下错误:如果在页面加载后执行脚本,则 popstate 事件会被阻止。

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.

Just run this code once before you add any onpopstate handlers and everything should work as expected (aka like in 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).

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.

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

天煞孤星 2024-11-23 18:39:34

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

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.

Here is my solution:
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);
});
微暖i 2024-11-23 18:39:34

解决方案已在 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
  }
})

The solution has been found in jquery.pjax.js lines 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
  }
})
百变从容 2024-11-23 18:39:34

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

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

$(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
}
め七分饶幸 2024-11-23 18:39:34

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>

虽然这个 < em>仍然可以触发两次(使用一些漂亮的导航),它可以通过检查之前的 href 来简单地处理。

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

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 could still fire twice (with some nifty navigation), it can be handled simply with a check against the previous href.

木落 2024-11-23 18:39:34

这是我的解决方法。

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

This is my workaround.

window.setTimeout(function() {
  window.addEventListener('popstate', function() {
    // ...
  });
}, 1000);
羁客 2024-11-23 18:39:34

这是我的解决方案:

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

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)');
        }
    }
})   
独留℉清风醉 2024-11-23 18:39:34

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

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!

又怨 2024-11-23 18:39:34

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

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

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

栩栩如生 2024-11-23 18:39:34

所提出的解决方案在页面重新加载时存在问题。以下似乎效果更好,但我只测试了 Firefox 和 Chrome。它使用了现实情况,即e.event.statewindow.history.state 之间似乎存在差异。

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

The presented solutions have a problem on page reload. The following seems to work better, but I have only tested Firefox and Chrome. It uses the actuality, that there seems to be a difference between e.event.state and window.history.state.

window.addEvent('popstate', function(e) {
    if(e.event.state) {
        window.location.reload(); // Event code
    }
});
愛放△進行李 2024-11-23 18:39:34

我知道你反对它,但你真的应该使用 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 上的 api .js

I know you asked against it, but you should really just use History.js as it clears up a million browser incompatibilities. I went the manual fix route only to later find there were more and more problems that you'll only find out way down the road. It really isn't that hard nowadays:

<script src="//cdnjs.cloudflare.com/ajax/libs/history.js/1.8/native.history.min.js" type="text/javascript"></script>

And read the api at https://github.com/browserstate/history.js

夢归不見 2024-11-23 18:39:34

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

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

This solved the problem for me. All I did was set a timeout function which delays the execution of the function long enough to miss the popstate event that is fired on pageload

if (history && history.pushState) {
  setTimeout(function(){
    $(window).bind("popstate", function() {
      $.getScript(location.href);
    });
  },3000);
}
橘寄 2024-11-23 18:39:34

您可以创建一个事件并在 onload 处理程序之后触发它。

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

请注意,这在 Chrome/Safari 中略有问题,但我已将补丁提交到 WebKit,它应该很快就会可用,但这是“最正确”的方式。

You can create an event and fire it after your onload handler.

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

Note, this is slightly broke in Chrome/Safari, but I have submitted the patch in to WebKit and it should be available soon, but it is the "most correct" way.

纵情客 2024-11-23 18:39:34

这在 Firefox 和 Chrome 中对我有用

window.onpopstate = function(event) { //back button click
    console.log("onpopstate");
    if (event.state) {
        window.location.reload();
    }
};

This worked for me in Firefox and Chrome

window.onpopstate = function(event) { //back button click
    console.log("onpopstate");
    if (event.state) {
        window.location.reload();
    }
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文