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

94

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

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

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

разделитель
источник
3
Чтобы добавить, поведение Firefox является заданным и правильным. Начиная с Chrome 34, это исправлено! Ура разработчикам Opera!
Генри Чан

Ответы:

49

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

Мое решение - добавить window.history.state! == null для проверки наличия состояния в window.history:

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

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

Павел Линкеш
источник
5
Спасибо за внимание; Я думаю, вы можете упростить inи nullпроверить, так != nullкак это тоже позаботится undefined.
pimvdb
5
Использование этого будет работать, только если вы всегда используете nullв своих history.pushState()методах, например history.pushState(null,null,'http//example.com/). Конечно, это, вероятно, большинство случаев использования (именно так он настроен в jquery.pjax.js и большинстве других демонстраций). Но если браузеры реализуют window.history.state(например, FF и Chrome 19+), window.history.state может быть ненулевым при обновлении или после перезапуска браузера
необъяснимоBacn
19
У меня это больше не работает в Chrome 21. Я закончил, просто установив значение false при загрузке страницы, а затем установил значение true при любом нажатии или всплытии. Это нейтрализует всплывающее окно Chrome при первой загрузке. Здесь это объяснено немного лучше: github.com/defunkt/jquery-pjax/issues/143#issuecomment-6194330
Чад фон Нау
1
@ChadvonNau отличная идея, и это работает - большое спасибо!
sowasred2012 07
У меня возникла та же проблема, и я решил использовать простое решение @ChadvonNau.
Pherrymason
30

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

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

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

Торбен
источник
4
Лучшее решение для меня. Я использую метод setTimeout в течение многих лет, но он вызывает ошибки, когда страница загружается медленно / когда пользователь очень быстро запускает pushstate. window.history.stateне выполняется при перезагрузке, если состояние было установлено. Установка переменной перед кодом pushState мешает. Ваше решение элегантно и может быть добавлено поверх других скриптов, не затрагивая остальную часть кода. Спасибо.
kik
4
Это решение! Если бы я только прочитал это несколько дней назад, пытаясь решить эту проблему. Это единственное, что сработало безупречно. Спасибо!
Бен Флеминг
1
Отличное объяснение и единственное решение, которое стоит попробовать.
Санни Р Гупта
Это отличное решение. На момент написания последней версии Chrome и FF проблема связана с сафари (Mac и iPhone). Это решение работает как шарм.
Vin
1
Woot! Если Safari портит вам день, это коды, которые вы ищете!
DanO
28

Использование только 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);
});
Сонни Пирс
источник
Это единственное решение, которое у меня сработало. Спасибо!
Джастин
3
сладкий! Работает лучше, чем самый одобренный и / или принятый ответ!
ProblemsOfSumit
Есть ли у кого-нибудь активная ссылка на это решение, я не могу добраться до данной ссылки gist.github.com/3551566
Харбир
1
зачем тебе суть?
Олег Васкевич
Прекрасный ответ. Спасибо.
sideroxylon
25

Решение было найдено в 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
  }
})
разделитель
источник
8
Это решение перестало работать для меня в Windows (Chrome 19), но все еще работает на Mac (Chrome 18). Похоже, что history.state существует в Chrome 19, но не в 18.
johnnymire
1
@johnnymire Я опубликовал свое решение для Chrome 19 ниже: stackoverflow.com/a/10651028/291500
Павел Линкеш
Кто-то должен отредактировать этот ответ, чтобы отразить это поведение после Chrome 19.
Хорхе Суарес де Лис
5
Для тех, кто ищет решение, ответ от Torben отлично подходит для текущих версий Chrome и Firefox на сегодняшний день
Хорхе Суарес де Лис
1
Теперь, в апреле 2015 года, я использовал это решение для решения проблемы с Safari. Мне пришлось использовать событие onpopstate, чтобы добиться обработки нажатия кнопки возврата в гибридном приложении iOS / Android с использованием Cordova. Safari запускает onpopstate даже после перезагрузки, которую я вызвал программно. Из-за того, как был настроен мой код, он перешел в бесконечный цикл после вызова reload (). Это исправило это. Мне нужны были только первые 3 строки после объявления события (плюс назначения вверху).
Мартавис П.
14

Более прямое решение, чем повторная реализация 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
}
Том Маккензи
источник
Разве это не было бы, если бы всегда было ложно? Я имею в виду, window.history.readyвсегда правда.
Андрей Дроздюк
Обновлен пример, чтобы он стал более понятным. По сути, вы просто хотите обрабатывать события popstate только после того, как вы дадите полную очистку. Вы можете добиться того же эффекта с setTimeout через 500 мс после загрузки, но, честно говоря, это немного дешево.
Том Маккензи
3
Это должен быть ответ. ИМО, я попробовал решение Pavels, и оно не работало должным образом в Firefox
Porco,
Это сработало для меня как простое решение этой досадной проблемы. Большое спасибо!
nirazul
!window.history.ready && !ev.originalEvent.state- эти две вещи никогда не определяются, когда я обновляю страницу. поэтому код не выполняется.
chovy
4

Начальному событию 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.

сом
источник
1
Спасибо. Решение pjax перестало работать в iOS 5.0, потому что window.history.stateв iOS оно также равно null. Достаточно просто проверить, имеет ли свойство e.state значение null.
Husky
Есть ли какие-нибудь известные проблемы с этим решением? Он отлично работает в любых браузерах, которые я тестировал.
Джейкоб Юинг,
3

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

window.setTimeout(function() {
  window.addEventListener('popstate', function() {
    // ...
  });
}, 1000);
Baggz
источник
Не для меня в Chromium 28 в Ubuntu. Иногда событие все же запускается.
Хорхе Суарес де Лис
Сам пробовал, не всегда получается. Иногда всплывающее сообщение срабатывает позже таймаута, в зависимости от начальной загрузки страницы
Эндрю Берджесс
1

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

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

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

Дэйв
источник
2
Это исправлено в Chrome 34.
matys84pl
0

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

иллюзионист
источник
window.onload = function () {if (history.state === null) {history.replaceState (location.pathname + location.search + location.hash, null, location.pathname + location.search + location.hash); }}; window.addEventListener ('popstate', function (e) {if (e.state! == null) {location.href = e.state;}}, false);
ilusionist
-1

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

window.addEvent('popstate', function(e) {
    if(e.event.state) {
        window.location.reload(); // Event code
    }
});
user2604836
источник
e.eventis undefined
chovy
-1

Я знаю, что вы возражали, но вам действительно стоит просто использовать 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

Убершмекель
источник
-1

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

if (history && history.pushState) {
  setTimeout(function(){
    $(window).bind("popstate", function() {
      $.getScript(location.href);
    });
  },3000);
}
Джереми Линч
источник
-2

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

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

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

Кинлан
источник
-2

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

window.onpopstate = function(event) { //back button click
    console.log("onpopstate");
    if (event.state) {
        window.location.reload();
    }
};
Алексей
источник