Popstate при завантаженні сторінки в Chrome


94

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

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

Отже, я хотів би знати, чи є спосіб зробити так, щоб Chrome не запускав `` popstate '' під час завантаження, або, можливо, хтось намагався використовувати History.js, оскільки всі бібліотеки об'єднуються в один файл.


3
До того ж, поведінка Firefox - це специфікована та правильна поведінка. Станом на Chrome 34, тепер це виправлено! Так для розробників опери!
Генрі Чан,

Відповіді:


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. Я завершив, просто встановивши значення popped на false при завантаженні сторінки, а потім встановивши значення true на будь-якому натисканні або спливаючому вікні. Це стерилізує сплеск Chrome при першому завантаженні. Тут це пояснюється дещо краще: github.com/defunkt/jquery-pjax/issues/143#issuecomment-6194330
Чад фон Нау

1
@ChadvonNau відмінна ідея, і це чудово працює - велике спасибі!
sowasred2012

Я прийшов з цією ж проблемою і закінчую використовувати просте рішення @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, поки не буде завантажено перший цикл події циклу після завантаження документа. Це робиться за допомогою викликів prevenDefault і stopImmediatePropagation (на відміну від stopPropagation stopImmediatePropagation миттєво зупиняє всі виклики обробника подій).

Однак, оскільки документ readyState вже увімкнено, коли Chrome помилково запускає onpopstate, ми дозволяємо події opopstate, які були запущені до завершення завантаження документа, щоб дозволити дзвінки onpopstate до завантаження документа.

Оновлення 2014-04-23: виправлена ​​помилка, коли події popstate блокувались, якщо сценарій виконувався після завантаження сторінки.


4
На сьогодні найкраще рішення для мене. Я використовую метод setTimeout роками, але він спричиняє поведінку помилок, коли сторінка завантажується повільно / коли користувач дуже швидко запускає pushstate. window.history.stateне вдається перезавантажити, якщо встановлено стан. Встановлення змінної перед кодом загрозу pushState. Ваше рішення елегантне і його можна скинути поверх інших сценаріїв, не впливаючи на решту кодової бази. Дякую.
kik

4
Це РІШЕННЯ! Якби я лише прочитав це ще кілька днів тому, намагаючись вирішити цю проблему. Це єдиний, який працював бездоганно. Дякую!
Бен Флемінг,

1
Відмінне пояснення та єдине рішення, яке варто спробувати.
Sunny R Gupta

Це чудове рішення. На момент написання останньої версії chrome & FF, проблема стосується сафарі (mac & iphone). Це рішення працює як шарм.
Він

1
Вуто! Якщо 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


1
навіщо тобі суть?
Олег Васкевич

Ідеальна відповідь. Дякую.
сидероксилон

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). Схоже,
історія. State

1
@johnnymire я відправив своє рішення для Chrome 19 нижче: stackoverflow.com/a/10651028/291500
Павло Linkesch

Хтось повинен відредагувати цю відповідь, щоб відобразити цю поведінку після Chrome 19.
Хорхе Суарес де Ліс

5
Для тих, хто шукає рішення, відповідь від Torben діє як привабливість у поточних версіях Chrome та Firefox на сьогодні
Хорхе Суарес де Ліс,

1
У квітні 2015 року я використовував це рішення для вирішення проблеми із Safari. Мені довелося використовувати подію onpopstate, щоб досягти обробки, натиснувши кнопку "Назад" у гібридному додатку iOS / Android за допомогою Cordova. Safari запускає свою onppstate навіть після перезавантаження, яке я назвав програмно. З тим, як був налаштований мій код, він перейшов у нескінченний цикл після виклику 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
Це має бути відповідь IMO, я спробував рішення Pavels, і воно не працювало належним чином у Firefox
Porco

Це працювало для мене як просте рішення цієї набридливої ​​проблеми. Дуже дякую!
ніразул

!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. Достатньо лише перевірити, чи властивість e.state є нульовим.
Husky

Чи є відомі проблеми з цим рішенням? Це чудово працює на будь-яких тестових браузерах.
Jacob Ewing

3

Це мій обхідний шлях.

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

Не для мене на 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', функція (e) {if (e.state! == null) {location.href = e.state;}}, false);
ілюзіоніст

-1

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

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

e.eventне визначено
човий

-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();
    }
};
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.