web-dev-qa-db-ja.com

ページ読み込み時のポップステートChrome

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を使用しようとしたのかどうかを知りたいファイル。

94
spliter

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でテストしました。動作するようです。

49
Pavel Linkesch

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イベントがブロックされるバグを修正しました。

30
Torben

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);
});
28
Sonny Piers

ソリューションは 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
  }
})
25
spliter

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
}
14
Tom McKenzie

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に対するチェックで簡単に処理できます。

4
som

これは私の回避策です。

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

私のソリューションは次のとおりです。

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

Chromeページの読み込み時にpopstateを起動しないようにする最良の方法は、投票することです https://code.google.com/p/chromium/issues/detail? id = 6304 。彼らが知っているChromeは2年間、HTML5仕様に準拠していないが、まだ修正していない!

0
Dave

使用する場合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);
0
ilusionist