Popstate saat halaman dimuat di Chrome

94

Saya menggunakan History API untuk aplikasi web saya dan memiliki satu masalah. Saya melakukan panggilan Ajax untuk memperbarui beberapa hasil pada halaman dan menggunakan history.pushState () untuk memperbarui bilah lokasi browser tanpa memuat ulang halaman. Kemudian, tentu saja, saya menggunakan window.popstate untuk mengembalikan keadaan sebelumnya saat tombol kembali diklik.

Masalahnya sudah diketahui - Chrome dan Firefox memperlakukan acara popstate itu secara berbeda. Meskipun Firefox tidak mengaktifkannya pada pemuatan pertama, Chrome melakukannya. Saya ingin memiliki gaya Firefox dan tidak mengaktifkan acara saat dimuat karena ini hanya memperbarui hasil dengan hasil yang persis sama saat dimuat. Apakah ada solusi selain menggunakan History.js ? Alasan saya tidak ingin menggunakannya adalah - ini membutuhkan terlalu banyak pustaka JS dengan sendirinya dan, karena saya membutuhkannya untuk diimplementasikan dalam CMS dengan JS yang sudah terlalu banyak, saya ingin meminimalkan JS yang saya masukkan ke dalamnya .

Jadi, ingin tahu apakah ada cara untuk membuat Chrome tidak menjalankan 'popstate' saat dimuat atau, mungkin, seseorang mencoba menggunakan History.js karena semua pustaka digabungkan menjadi satu file.

spliter
sumber
3
Sebagai tambahan, perilaku Firefox adalah perilaku yang ditentukan dan benar. Mulai Chrome 34, sekarang sudah diperbaiki! Hore untuk pengembang Opera!
Henry Chan

Jawaban:

49

Di Google Chrome versi 19, solusi dari @spliter berhenti bekerja. Seperti yang ditunjukkan @johnnymire, history.state di Chrome 19 ada, tetapi null.

Solusi saya adalah menambahkan window.history.state! == null untuk memeriksa apakah status ada di window.history:

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

Saya mengujinya di semua browser utama dan di Chrome versi 19 dan 18. Sepertinya berhasil.

Pavel Linkesch
sumber
5
Terimakasih atas peringatannya; Saya pikir Anda dapat menyederhanakan indan nullmemeriksa hanya != nullkarena itu akan mengurusnya undefinedjuga.
pimvdb
5
Menggunakan ini hanya akan berfungsi jika Anda selalu menggunakan nulldalam history.pushState()metode seperti history.pushState(null,null,'http//example.com/). Memang, itu mungkin sebagian besar penggunaan (begitulah cara menyiapkannya di jquery.pjax.js dan sebagian besar demo lainnya). Tetapi jika browser mengimplementasikan window.history.state(seperti FF dan Chrome 19+), window.history.state bisa menjadi non-null saat penyegaran atau setelah browser dimulai ulang
unexplainedBacn
19
Ini tidak berfungsi lagi untuk saya di Chrome 21. Saya akhirnya hanya mengatur muncul ke false pada pemuatan halaman dan kemudian pengaturan muncul ke true pada setiap push atau pops. Ini menghentikan pop Chrome saat pertama kali dimuat. Ini dijelaskan sedikit lebih baik di sini: github.com/defunkt/jquery-pjax/issues/143#issuecomment-6194330
Chad von Nau
1
@ChadvonNau ide bagus, dan berhasil - terima kasih banyak!
sowasred2012
Saya datang dengan masalah yang sama ini dan mengakhiri dengan solusi @ChadvonNau sederhana.
Pherrymason
30

Jika Anda tidak ingin mengambil tindakan khusus untuk setiap penangan yang Anda tambahkan ke onpopstate, solusi saya mungkin menarik untuk Anda. Nilai tambah yang besar dari solusi ini juga bahwa event onpopstate dapat ditangani sebelum pemuatan halaman selesai.

Jalankan saja kode ini sekali sebelum Anda menambahkan penangan onpopstate dan semuanya akan bekerja seperti yang diharapkan (alias seperti di 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);
})();

Bagaimana itu bekerja:

Chrome , Safari dan mungkin browser webkit lainnya mengaktifkan acara onpopstate saat dokumen telah dimuat. Ini tidak dimaksudkan, jadi kami memblokir event popstate hingga event loop pertama setelah dokumen dimuat. Ini dilakukan oleh panggilan preventDefault dan stopImmediatePropagation (tidak seperti stopPropagation, stopImmediatePropagation menghentikan semua panggilan penangan peristiwa secara instan).

Namun, karena readyState dokumen sudah "selesai" saat Chrome salah diaktifkan di popstate, kami mengizinkan peristiwa opopstate, yang telah diaktifkan sebelum pemuatan dokumen selesai untuk memungkinkan panggilan onpopstate sebelum dokumen dimuat.

Pembaruan 2014-04-23: Memperbaiki bug di mana acara popstate telah diblokir jika skrip dijalankan setelah halaman dimuat.

Torben
sumber
4
Solusi terbaik sejauh ini untuk saya. Saya telah menggunakan metode setTimeout selama bertahun-tahun, tetapi itu menyebabkan perilaku buggy saat halaman dimuat dengan lambat / ketika pengguna memicu pushstate dengan sangat cepat. window.history.stategagal memuat ulang jika status telah ditetapkan. Menyetel variabel sebelum kode clutters pushState. Solusi Anda elegan dan dapat ditempatkan di atas skrip lain, tanpa memengaruhi basis kode lainnya. Terima kasih.
kik
4
Inilah solusinya! Kalau saja saya membaca ini jauh-jauh hari yang lalu ketika mencoba memecahkan masalah ini. Ini adalah satu-satunya yang telah bekerja dengan sempurna. Terima kasih!
Ben Fleming
1
Penjelasan luar biasa dan satu-satunya solusi yang patut dicoba.
Cerah R Gupta
Ini adalah solusi yang bagus. Pada saat penulisan, chrome & FF terbaru, masalahnya ada pada safari (mac & iphone). Solusi ini bekerja dengan sangat baik.
Vin
1
Woot! Jika Safari merusak hari Anda, ini adalah kode yang Anda cari!
DanO
28

Menggunakan setTimeout saja bukanlah solusi yang tepat karena Anda tidak tahu berapa lama waktu yang dibutuhkan untuk konten dimuat sehingga mungkin kejadian popstate dipancarkan setelah batas waktu.

Inilah solusi saya: 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);
});
Sonny Piers
sumber
Ini adalah satu-satunya solusi yang berhasil untuk saya. Terima kasih!
Justin
3
manis! Berfungsi lebih baik daripada jawaban yang paling disukai dan / atau diterima!
ProblemsOfSumit
Apakah ada yang memiliki tautan aktif ke solusi ini, saya tidak dapat menjangkau tautan yang diberikan gist.github.com/3551566
Harbir
1
mengapa Anda membutuhkan intinya?
Oleg Vaskevich
Jawaban yang sempurna. Terima kasih.
sideroxylon
25

Solusi telah ditemukan di jquery.pjax.js baris 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
  }
})
spliter
sumber
8
Solusi ini berhenti berfungsi untuk saya di Windows (Chrome 19), masih berfungsi di Mac (Chrome 18). Sepertinya history.state ada di Chrome 19 tetapi tidak 18.
johnnymire
1
@johnnymire Saya memposting solusi saya untuk Chrome 19 lebih rendah: stackoverflow.com/a/10651028/291500
Pavel Linkesch
Seseorang harus mengedit jawaban ini untuk mencerminkan perilaku pasca-Chrome 19 ini.
Jorge Suárez de Lis
5
Bagi siapa pun yang mencari solusi, jawaban dari Torben bekerja seperti pesona pada versi Chrome dan Firefox saat ini
Jorge Suárez de Lis
1
Sekarang di bulan April 2015, saya menggunakan solusi ini untuk menyelesaikan masalah dengan Safari. Saya harus menggunakan acara onpopstate untuk mencapai penanganan dengan menekan tombol kembali pada aplikasi iOS / Android hybrid menggunakan Cordova. Safari mengaktifkan status onpopnya bahkan setelah memuat ulang, yang saya sebut secara terprogram. Dengan cara kode saya disiapkan, itu masuk ke loop tak terbatas setelah reload () dipanggil. Ini memperbaikinya. Saya hanya membutuhkan 3 baris pertama setelah deklarasi acara (ditambah tugas di atas).
Martavis P.
14

Solusi yang lebih langsung daripada menerapkan ulang pjax adalah menetapkan variabel di pushState, dan memeriksa variabel di popState, sehingga popState awal tidak aktif secara tidak konsisten saat dimuat (bukan solusi khusus jquery, hanya menggunakannya untuk acara):

$(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
}
Tom McKenzie
sumber
Bukankah itu jika selalu bernilai salah? Maksud saya, window.history.readyselalu benar.
Andriy Drozdyuk
Memperbarui contoh menjadi sedikit lebih jelas. Pada dasarnya, Anda hanya ingin menangani acara popstate setelah Anda memberikan semuanya jelas. Anda dapat mencapai efek yang sama dengan setTimeout 500ms setelah pemuatan, tetapi sejujurnya itu agak murah.
Tom McKenzie
3
Ini harus menjadi jawaban IMO, saya mencoba solusi Pavels dan tidak berfungsi dengan baik di Firefox
Porco
Ini bekerja untuk saya sebagai solusi mudah untuk masalah yang mengganggu ini. Terima kasih banyak!
nirazul
!window.history.ready && !ev.originalEvent.state- kedua hal itu tidak pernah ditentukan saat saya menyegarkan laman. jadi karena itu tidak ada kode yang dieksekusi.
chovy
4

Peristiwa onpopstate awal Webkit tidak memiliki status yang ditetapkan, jadi Anda dapat menggunakan ini untuk memeriksa perilaku yang tidak diinginkan:

window.onpopstate = function(e){
    if(e.state)
        //do something
};

Solusi komprehensif, yang memungkinkan navigasi kembali ke halaman asli, akan dibangun berdasarkan ide ini:

<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>

Meskipun ini masih dapat diaktifkan dua kali (dengan beberapa navigasi yang bagus), ini dapat ditangani hanya dengan memeriksa href sebelumnya.

som
sumber
1
Terima kasih. Solusi pjax berhenti berfungsi di iOS 5.0 karena window.history.statejuga null di iOS. Hanya memeriksa apakah properti e.state null sudah cukup.
Husky
Apakah ada masalah yang diketahui dengan solusi ini? Ia bekerja dengan sempurna di semua browser yang saya uji.
Jacob Ewing
3

Ini adalah solusi saya.

window.setTimeout(function() {
  window.addEventListener('popstate', function() {
    // ...
  });
}, 1000);
Baggz
sumber
Bukan untuk saya di Chromium 28 di Ubuntu. Terkadang, acara masih dipecat.
Jorge Suárez de Lis
Mencoba ini sendiri, tidak selalu berhasil. Kadang-kadang popstate aktif lebih lambat dari waktu tunggu tergantung pada pemuatan halaman awal
Andrew Burgess
1

Inilah solusi saya:

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

Cara terbaik agar Chrome tidak mengaktifkan popstate pada saat pemuatan halaman adalah dengan memilih-lebih https://code.google.com/p/chromium/issues/detail?id=63040 . Mereka tahu bahwa Chrome tidak mematuhi spesifikasi HTML5 selama dua tahun penuh sekarang dan masih belum memperbaikinya!

Dave
sumber
2
Ini telah diperbaiki sejak Chrome 34.
matys84pl
0

Jika digunakan, event.state !== nullkembali ke riwayat ke halaman yang dimuat pertama tidak akan berfungsi di browser non seluler. Saya menggunakan sessionStorage untuk menandai kapan navigasi ajax benar-benar dimulai.

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

ilusionis
sumber
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

Solusi yang disajikan memiliki masalah saat memuat ulang halaman. Berikut ini tampaknya berfungsi lebih baik, tetapi saya hanya menguji Firefox dan Chrome. Ini menggunakan aktualitas, bahwa tampaknya ada perbedaan antara e.event.statedan window.history.state.

window.addEvent('popstate', function(e) {
    if(e.event.state) {
        window.location.reload(); // Event code
    }
});
pengguna2604836
sumber
e.eventtidak ditentukan
chovy
-1

Saya tahu Anda bertanya untuk tidak melakukannya, tetapi Anda harus benar-benar menggunakan History.js karena menghapus jutaan inkompatibilitas browser. Saya pergi ke rute perbaikan manual hanya untuk kemudian menemukan ada lebih banyak masalah yang hanya akan Anda temukan di jalan. Sebenarnya tidak terlalu sulit saat ini:

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

Dan baca api di https://github.com/browserstate/history.js

ubershmekel
sumber
-1

Ini memecahkan masalah saya. Yang saya lakukan hanyalah menyetel fungsi batas waktu yang menunda pelaksanaan fungsi cukup lama untuk melewatkan acara popstate yang diaktifkan di pageload

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

Anda dapat membuat acara dan mengaktifkannya setelah onload handler Anda.

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

Catatan, ini sedikit rusak di Chrome / Safari, tetapi saya telah mengirimkan tambalan ke WebKit dan itu akan segera tersedia, tetapi ini adalah cara yang "paling benar".

Kinlan
sumber
-2

Ini berfungsi untuk saya di Firefox dan Chrome

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