WebアプリにHistory APIを使用していますが、1つの問題があります。ページの一部の結果を更新するためにAjax呼び出しを行い、history.pushState()を使用して、ページをリロードせずにブラウザーのロケーションバーを更新します。それから、もちろん、戻るボタンがクリックされたときに以前の状態を復元するために、window.popstateを使用します。
問題はよく知られています— ChromeとFirefoxはそのpopstateイベントを異なる方法で処理します。Firefoxは最初のロードで起動しませんが、Chromeは処理します。読み込み時にイベントをまったく同じ結果に更新するだけなので、Firefoxスタイルで読み込み時にイベントを発生させたくないのですが、 History.js を使用する以外の回避策はありますか?それを使用する気はありません-それはそれだけで非常に多くのJSライブラリを必要とし、すでに多すぎるJSでCMSに実装する必要があるので、私はそれを入れているJSを最小限に抑えたいと思います。
だから、Chromeロード時に 'popstate'を起動しないようにする方法があるのか、あるいは、すべてのライブラリが1つにマッシュアップされたときに誰かがHistory.jsを使用しようとしたのかどうかを知りたいファイル。
Googleでは、Chromeバージョン19では、@ spliterのソリューションは機能しませんでした。@ johnnymireが指摘したように、history.state in Chrome 19は存在しますが、nullです。
私の回避策は、window.history.state!== nullをwindow.historyに状態が存在するかどうかのチェックに追加することです:
var popped = ('state' in window.history && window.history.state !== null), initialURL = location.href;
すべての主要なブラウザとChromeバージョン19および18でテストしました。動作するようです。
Onpopstateに追加する各ハンドラーに対して特別な措置を講じたくない場合、私の解決策は興味深いかもしれません。このソリューションの大きな利点は、ページの読み込みが完了する前にonpopstateイベントを処理できることです。
Onpopstateハンドラを追加する前にこのコードを1回実行するだけで、すべてが期待どおりに動作するはずです(別名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イベントを発生させます。これは意図されていないため、ドキュメントがロードされた後の最初のイベントループcicleまでpopstateイベントをブロックします。これはpreventDefaultおよびstopImmediatePropagation呼び出しによって行われます(stopPropagationとは異なり、stopImmediatePropagationはすべてのイベントハンドラー呼び出しを即座に停止します)。
ただし、Chromeが誤ってonpopstateを起動する場合、ドキュメントのreadyStateはすでに「完了」になっているため、ドキュメントの読み込みが完了する前に起動されるopopstateイベントを許可して、ドキュメントが完了する前にonpopstate呼び出しを許可しますロードされました。
2014-04-23の更新:ページが読み込まれた後にスクリプトが実行されるとpopstateイベントがブロックされるバグを修正しました。
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);
});
ソリューションは 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
}
})
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
}
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>
このcouldはまだ2回起動しますが(ナビゲーションが少し優れています)、前のhrefに対するチェックで簡単に処理できます。
これは私の回避策です。
window.setTimeout(function() {
window.addEventListener('popstate', function() {
// ...
});
}, 1000);
私のソリューションは次のとおりです。
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)');
}
}
})
Chromeページの読み込み時にpopstateを起動しないようにする最良の方法は、投票することです https://code.google.com/p/chromium/issues/detail? id = 6304 。彼らが知っているChromeは2年間、HTML5仕様に準拠していないが、まだ修正していない!
使用する場合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);