Popstate при загрузке страницы в Chrome - PullRequest
94 голосов
/ 21 июня 2011

Я использую History API для своего веб-приложения, и у меня есть одна проблема.Я делаю Ajax-вызовы, чтобы обновить некоторые результаты на странице и использую history.pushState () , чтобы обновить строку адреса браузера без перезагрузки страницы.Затем, конечно, я использую window.popstate , чтобы восстановить предыдущее состояние при нажатии кнопки назад.

Проблема хорошо известна - Chrome и Firefox по-разному обрабатывают событие popstate,Хотя Firefox не запускает его при первой загрузке, Chrome запускает.Я хотел бы иметь Firefox-стиль и не запускать событие при загрузке, так как он просто обновляет результаты с точно такими же при загрузке.Есть ли обходной путь, кроме использования History.js ?Причина, по которой мне не хочется его использовать, заключается в том, что для него нужно слишком много библиотек JS, и, поскольку мне нужно, чтобы он был реализован в CMS с уже слишком большим количеством JS, я хотел бы свести к минимуму количество JS, которое я в него помещаю..

Итак, хотелось бы узнать, есть ли способ заставить Chrome не запускать popstate при загрузке или, может быть, кто-то пытался использовать History.js, поскольку все библиотеки объединены в один файл.

Ответы [ 15 ]

49 голосов
/ 18 мая 2012

В Google Chrome в версии 19 перестало работать решение от @spliter.Как указал @johnnymire, history.state в 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. Похоже, он работает.

30 голосов
/ 08 августа 2013

Если вы не хотите принимать специальные меры для каждого обработчика, который вы добавляете в 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 до первого цикла цикла событий после загрузки документа.Это делается с помощью вызовов warnDefault и stopImmediatePropagation (в отличие от stopPropagation stopImmediatePropagation мгновенно останавливает все вызовы обработчика событий).

Однако, поскольку readyState документа уже находится в состоянии «завершено», когда Chrome вызывает ошибки onpopstate по ошибке, мы разрешаем события opopstate,которые были запущены до завершения загрузки документа, чтобы разрешить вызовы onpopstate до загрузки документа.

Обновление 2014-04-23: Исправлена ​​ошибка, при которой события popstate блокировались, еслискрипт выполняется после загрузки страницы.

28 голосов
/ 31 августа 2012

Использование только 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);
});
25 голосов
/ 07 августа 2011

Решение найдено в 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
  }
})
14 голосов
/ 15 августа 2011

Более прямое решение, чем повторная реализация 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
}
4 голосов
/ 12 мая 2012

Исходное событие onpopstate Webkit не имеет назначенного состояния, поэтому вы можете использовать его для проверки нежелательного поведения:

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>

Хотя этот может по-прежнему срабатывать дважды (с некоторой изящной навигацией), его можно обработать, просто проверив предыдущую ссылку.

3 голосов
/ 05 декабря 2011

Это мой обходной путь.

window.setTimeout(function() {
  window.addEventListener('popstate', function() {
    // ...
  });
}, 1000);
1 голос
/ 15 ноября 2012

Вот мое решение:

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)');
        }
    }
})   
0 голосов
/ 16 апреля 2016

В случае использования 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 голосов
/ 06 февраля 2014

Это решило проблему для меня. Все, что я сделал, это установил функцию тайм-аута, которая задерживает выполнение функции достаточно долго, чтобы пропустить событие popstate, которое запускается при загрузке страницы

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