Popstate เมื่อโหลดหน้าเว็บใน Chrome


94

ฉันใช้ History API สำหรับเว็บแอปของฉันและมีปัญหาหนึ่งอย่าง ฉันเรียก Ajax เพื่ออัปเดตผลลัพธ์บางอย่างบนเพจและใช้history.pushState ()เพื่ออัปเดตแถบตำแหน่งของเบราว์เซอร์โดยไม่ต้องโหลดหน้าซ้ำ จากนั้นฉันใช้window.popstateเพื่อกู้คืนสถานะก่อนหน้าเมื่อคลิกปุ่มย้อนกลับ

ปัญหาเป็นที่ทราบกันดี - Chrome และ Firefox ปฏิบัติต่อเหตุการณ์ป๊อปสเตทแตกต่างกัน แม้ว่า Firefox จะไม่ทำงานในการโหลดครั้งแรก แต่ Chrome ก็ทำเช่นนั้น ฉันต้องการใช้รูปแบบ Firefox และไม่ทำให้เหตุการณ์เกิดขึ้นเมื่อโหลดเนื่องจากเพิ่งอัปเดตผลลัพธ์โดยให้ผลลัพธ์เดียวกันกับที่โหลด มีวิธีแก้ปัญหายกเว้นการใช้History.jsหรือไม่ เหตุผลที่ฉันไม่รู้สึกอยากใช้คือ - มันต้องการไลบรารี JS มากเกินไปด้วยตัวเองและเนื่องจากฉันต้องการให้ใช้งานใน CMS ที่มี JS มากเกินไปฉันจึงต้องการลด JS ที่ฉันใส่ลงไป .

ดังนั้นอยากทราบว่ามีวิธีที่จะทำให้ Chrome ไม่เปิด 'popstate' เมื่อโหลดได้หรือไม่หรืออาจมีบางคนพยายามใช้ History.js เนื่องจากไลบรารีทั้งหมดรวมเข้าด้วยกันเป็นไฟล์เดียว


3
หากต้องการเพิ่มพฤติกรรมของ Firefox เป็นพฤติกรรมที่ระบุและถูกต้อง สำหรับ Chrome 34 ตอนนี้ได้รับการแก้ไขแล้ว! ใช่สำหรับผู้พัฒนา Opera!
Henry

คำตอบ:


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 อาจไม่เป็นค่าว่างในการรีเฟรชหรือหลังจากเบราว์เซอร์รีสตาร์ท
unexplainedBacn

19
สิ่งนี้ใช้ไม่ได้สำหรับฉันใน Chrome 21 อีกต่อไปฉันตัดการตั้งค่าเพียงแค่การตั้งค่า popped เป็น false ในการโหลดหน้าเว็บจากนั้นการตั้งค่า popped เป็น true สำหรับการพุชหรือป๊อปใด ๆ สิ่งนี้ขัดขวางป๊อปของ Chrome ในการโหลดครั้งแรก อธิบายได้ดีกว่าเล็กน้อยที่นี่: github.com/defunkt/jquery-pjax/issues/143#issuecomment-6194330
Chad von Nau

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 เมื่อโหลดเอกสารแล้ว สิ่งนี้ไม่ได้ตั้งใจดังนั้นเราจึงบล็อกเหตุการณ์ที่แตกต่างกันจนกว่าจะมีการวนรอบเหตุการณ์แรกหลังจากโหลดเอกสารแล้ว สิ่งนี้ทำได้โดยการเรียก PreventDefault และ stopImmediatePropagation (ซึ่งแตกต่างจาก stopPropagation stopImmediatePropagation จะหยุดการเรียกตัวจัดการเหตุการณ์ทั้งหมดทันที)

อย่างไรก็ตามเนื่องจาก readyState ของเอกสารอยู่ในสถานะ "เสร็จสมบูรณ์" อยู่แล้วเมื่อ Chrome เริ่มทำงานบนป๊อปสเตทอย่างไม่ถูกต้องเราจึงอนุญาตให้มีเหตุการณ์ opopstate ซึ่งเกิดขึ้นก่อนที่การโหลดเอกสารจะเสร็จสิ้นเพื่อให้สามารถเรียก onpopstate ได้ก่อนที่เอกสารจะโหลด

อัปเดต 2014-04-23:แก้ไขข้อผิดพลาดที่เหตุการณ์ป๊อปสเตทถูกบล็อกหากสคริปต์ถูกเรียกใช้งานหลังจากโหลดเพจแล้ว


4
ทางออกที่ดีที่สุดสำหรับฉัน ฉันใช้เมธอด setTimeout มาหลายปีแล้ว แต่มันทำให้เกิดพฤติกรรมบั๊กกี้เมื่อเพจโหลดช้า / เมื่อผู้ใช้ทริกเกอร์ pushstate เร็วมาก window.history.stateล้มเหลวในการโหลดซ้ำหากตั้งค่าสถานะไว้ การตั้งค่าตัวแปรก่อนโค้ด pushState clutters โซลูชันของคุณสวยงามและสามารถวางทับบนสคริปต์อื่น ๆ ได้โดยไม่ส่งผลกระทบต่อส่วนที่เหลือของโค้ดเบส ขอบคุณ.
kik

4
นี่คือทางออก! ถ้าเพียงฉันอ่านเรื่องนี้เมื่อหลายวันก่อนเมื่อพยายามแก้ปัญหานี้ นี่เป็นเพียงคนเดียวที่ทำงานได้อย่างไม่มีที่ติ ขอขอบคุณ!
Ben Fleming

1
คำอธิบายที่ยอดเยี่ยมและทางออกเดียวที่ควรลอง
Sunny R Gupta

นี่เป็นทางออกที่ยอดเยี่ยม ในขณะที่เขียน chrome & FF fine ล่าสุดปัญหาอยู่ที่ Safari (mac & iphone) วิธีนี้ใช้งานได้เหมือนมีเสน่ห์
Vin

1
ว๊อท! หาก Safari กำลังทำลายวันของคุณนี่คือรหัสที่คุณกำลังมองหา!
DanO

28

การใช้ setTimeout เท่านั้นไม่ใช่วิธีแก้ปัญหาที่ถูกต้องเนื่องจากคุณไม่รู้ว่าจะต้องใช้เวลานานแค่ไหนในการโหลดเนื้อหาดังนั้นจึงเป็นไปได้ว่าเหตุการณ์ป๊อปสเตทจะถูกปล่อยออกมาหลังจากหมดเวลา

นี่คือวิธีแก้ปัญหาของฉัน: 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);
});

นี่เป็นทางออกเดียวที่ใช้ได้ผลสำหรับฉัน ขอบคุณ!
Justin

3
หวาน! ทำงานได้ดีกว่าคำตอบที่ได้รับการโหวตและ / หรือได้รับการยอมรับมากที่สุด!
ProblemsOfSumit

ใครมีลิงก์ที่ใช้งานไปยังโซลูชันนี้ฉันไม่สามารถเข้าถึงลิงก์ที่ระบุได้gist.github.com/3551566
Harbir

1
ทำไมคุณถึงต้องการความสำคัญ?
Oleg Vaskevich

คำตอบที่สมบูรณ์แบบ ขอบคุณ.
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
Pavel Linkesch

ใครบางคนควรแก้ไขคำตอบนี้เพื่อให้สอดคล้องกับพฤติกรรมหลัง Chrome 19 นี้
Jorge Suárez de Lis

5
สำหรับใครก็ตามที่กำลังมองหาวิธีแก้ปัญหาคำตอบจาก Torben ใช้งานได้ดีใน Chrome และ Firefox เวอร์ชันปัจจุบัน ณ วันนี้
Jorge Suárez de Lis

1
ตอนนี้ในเดือนเมษายน 2015 ฉันใช้วิธีนี้เพื่อแก้ปัญหากับ Safari ฉันต้องใช้เหตุการณ์ onpopstate เพื่อให้สามารถจัดการได้โดยกดปุ่มย้อนกลับบนแอปไฮบริด iOS / Android โดยใช้ Cordova Safari ยิง onpopstate แม้จะโหลดซ้ำซึ่งฉันเรียกว่าแบบเป็นโปรแกรม ด้วยวิธีการตั้งค่ารหัสของฉันมันจะเข้าสู่ลูปที่ไม่มีที่สิ้นสุดหลังจากเรียกการโหลดซ้ำ () สิ่งนี้แก้ไขได้ ฉันต้องการเพียง 3 บรรทัดแรกหลังจากการประกาศเหตุการณ์ (รวมถึงงานที่มอบหมายที่ด้านบน)
Martavis P.

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เป็นความจริงเสมอ
Andriy Drozdyuk

ปรับปรุงตัวอย่างให้ชัดเจนขึ้นอีกนิด โดยพื้นฐานแล้วคุณเพียงแค่ต้องการจัดการกับเหตุการณ์ที่เกิดขึ้นเมื่อคุณให้ข้อมูลที่ชัดเจนทั้งหมด คุณสามารถบรรลุเอฟเฟกต์เดียวกันกับ setTimeout 500ms หลังจากโหลด แต่ความจริงแล้วมันถูกไปหน่อย
Tom McKenzie

3
นี่ควรเป็นคำตอบ IMO ฉันลองใช้โซลูชัน 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 เพียงตรวจสอบว่าคุณสมบัติ e.state เป็นโมฆะก็เพียงพอหรือไม่
Husky

มีปัญหาที่ทราบเกี่ยวกับโซลูชันนี้หรือไม่? ทำงานได้อย่างสมบูรณ์บนเบราว์เซอร์ใด ๆ ที่ฉันทดสอบ
Jacob Ewing

3

นี่คือวิธีแก้ปัญหาของฉัน

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

ไม่ใช่สำหรับฉันใน Chromium 28 บน Ubuntu บางครั้งงานยังลั่น
Jorge Suárez de Lis

พยายามด้วยตัวเองไม่ได้ผลเสมอไป บางครั้ง popstate ไฟช้ากว่าหมดเวลาขึ้นอยู่กับการโหลดหน้าเริ่มต้น
แอนดรู Burgess

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

-1

วิธีแก้ไขปัญหาที่นำเสนอมีปัญหาในการโหลดหน้าซ้ำ สิ่งต่อไปนี้ดูเหมือนจะทำงานได้ดีขึ้น แต่ฉันได้ทดสอบ Firefox และ Chrome เท่านั้น มันใช้จริงที่ดูเหมือนว่าจะมีความแตกต่างระหว่างและe.event.statewindow.history.state

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

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

สิ่งนี้ช่วยแก้ปัญหาให้ฉันได้ สิ่งที่ฉันทำคือตั้งค่าฟังก์ชันการหมดเวลาซึ่งจะทำให้การทำงานของฟังก์ชันล่าช้านานพอที่จะพลาดเหตุการณ์ป๊อปสเตตที่เริ่มทำงานบน pageload

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();
    }
};
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.