Popstate на загрузке страницы в Chrome

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

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

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

Ответ 1

В Google Chrome в версии 19 решение от @spliter перестало работать. Как отметил @johnymymire, history.state в Chrome 19 существует, но он null.

Мое обходное решение заключается в том, чтобы добавить window.history.state! == null в проверку, существует ли состояние в window.history:

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

Я тестировал его во всех основных браузерах и в версиях 19 и 18 Chrome. Похоже, что он работает.

Ответ 2

Если вы не хотите принимать специальные меры для каждого обработчика, добавляемого в onpopstate, мое решение может быть интересно для вас. Большим плюсом этого решения является также то, что события onpopstate могут быть обработаны до того, как загрузка страницы будет завершена.

Просто запустите этот код один раз, прежде чем добавлять какие-либо обработчики onpopstate, и все должно работать как ожидается (иначе как в Mozilla ^^).

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

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

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

Ответ 3

Использование 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);
});

Ответ 4

Решение найдено в 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
  }
})

Ответ 5

Более прямое решение, чем переопределение 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
}

Ответ 6

Первоначальное событие 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>

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

Ответ 7

Это мое обходное решение.

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

Ответ 8

Здесь мое решение:

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

Ответ 9

Лучший способ заставить Chrome не запускать popstate при загрузке страницы - это проголосовать https://code.google.com/p/chromium/issues/detail?id=63040. Они знают, что Chrome не соответствует спецификации HTML5 в течение двух полных лет и до сих пор не исправил ее!

Ответ 10

В случае использования 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);

Ответ 11

Представленные решения имеют проблемы при перезагрузке страницы. Следующее, похоже, работает лучше, но я тестировал только Firefox и Chrome. Он использует актуальность, что, кажется, существует разница между e.event.state и window.history.state.

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

Ответ 12

Я знаю, что вы просили его, но вы действительно должны просто использовать History.js, поскольку он очищает миллион несовместимости браузера. Я отправился на путь исправления вручную, чтобы позже найти все больше и больше проблем, которые вы только узнаете по дороге. В настоящее время это не так сложно:

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

И прочитайте api в https://github.com/browserstate/history.js

Ответ 13

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

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

Ответ 14

Вы можете создать событие и запустить его после обработчика onload.

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

Заметьте, что это немного сломалось в Chrome/Safari, но я отправил патч в WebKit, и он скоро будет доступен, но это "самый правильный" способ.

Ответ 15

Это работало для меня в Firefox и Chrome

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