Phổ biến khi tải trang trong Chrome


94

Tôi đang sử dụng API Lịch sử cho ứng dụng web của mình và gặp một vấn đề. Tôi thực hiện lệnh gọi Ajax để cập nhật một số kết quả trên trang và sử dụng history.pushState () để cập nhật thanh vị trí của trình duyệt mà không cần tải lại trang. Sau đó, tất nhiên, tôi sử dụng window.popstate để khôi phục trạng thái trước đó khi nhấp vào nút quay lại.

Vấn đề đã được biết đến nhiều - Chrome và Firefox xử lý sự kiện popstate đó theo cách khác nhau. Mặc dù Firefox không kích hoạt nó trong lần tải đầu tiên, nhưng Chrome thì có. Tôi muốn có kiểu Firefox và không kích hoạt sự kiện khi tải vì nó chỉ cập nhật kết quả với chính xác những kết quả khi tải. Có giải pháp nào khác ngoại trừ sử dụng History.js không? Lý do tôi không thích sử dụng nó là - bản thân nó cần quá nhiều thư viện JS và vì tôi cần nó được triển khai trong một CMS với quá nhiều JS, tôi muốn giảm thiểu JS mà tôi đang đưa vào. .

Vì vậy, muốn biết liệu có cách nào để khiến Chrome không kích hoạt 'popstate' khi tải hoặc có thể, ai đó đã cố gắng sử dụng History.js khi tất cả các thư viện được trộn với nhau thành một tệp.


3
Nói thêm, hành vi của Firefox là hành vi được suy đoán và đúng. Kể từ Chrome 34, nó hiện đã được khắc phục! Yay cho các nhà phát triển Opera!
Henry Chan

Câu trả lời:


49

Trong Google Chrome ở phiên bản 19, giải pháp từ @spliter đã ngừng hoạt động. Như @johnnymire đã chỉ ra, history.state trong Chrome 19 tồn tại, nhưng nó vô hiệu.

Cách giải quyết của tôi là thêm window.history.state! == null vào kiểm tra xem trạng thái có tồn tại trong window.history:

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

Tôi đã thử nghiệm nó trên tất cả các trình duyệt chính và trong Chrome phiên bản 19 và 18. Có vẻ như nó hoạt động.


5
Cảm ơn cho những người đứng đầu lên; Tôi nghĩ rằng bạn có thể đơn giản hóa innullkiểm tra chỉ != nullvì điều đó cũng sẽ chăm sóc undefined.
pimvdb

5
Sử dụng điều này sẽ chỉ hoạt động nếu bạn luôn sử dụng nulltrong các history.pushState()phương pháp của mình như history.pushState(null,null,'http//example.com/). Được cấp, đó có thể là hầu hết các cách sử dụng (đó là cách nó được thiết lập trong jquery.pjax.js và hầu hết các bản trình diễn khác). Nhưng nếu các trình duyệt triển khai window.history.state(như FF và Chrome 19+), window.history.state có thể không có giá trị khi làm mới hoặc sau khi khởi động lại trình duyệt
không giải thích

19
Tính năng này không còn hoạt động đối với tôi trong Chrome 21. Tôi chỉ thiết lập cửa sổ bật lên thành false khi tải trang và sau đó đặt cửa sổ bật lên thành true khi đẩy hoặc bật bất kỳ cửa sổ bật lên nào. Bộ định tuyến này của Chrome bật lên trong lần tải đầu tiên. Nó được giải thích tốt hơn một chút ở đây: github.com/defunkt/jquery-pjax/issues/143#issuecomment-6194330
Chad von Nau

1
@ChadvonNau ý tưởng tuyệt vời và nó rất hiệu quả - cảm ơn rất nhiều!
gieoasred2012 Ngày

Tôi đã gặp cùng một vấn đề này và kết thúc bằng giải pháp @ChadvonNau đơn giản.
Pherrymason

30

Trong trường hợp bạn không muốn thực hiện các biện pháp đặc biệt cho từng trình xử lý mà bạn thêm vào onpopstate, giải pháp của tôi có thể thú vị với bạn. Một điểm cộng lớn của giải pháp này là các sự kiện onpopstate có thể được xử lý trước khi quá trình tải trang hoàn tất.

Chỉ cần chạy mã này một lần trước khi bạn thêm bất kỳ trình xử lý onpopstate nào và mọi thứ sẽ hoạt động như mong đợi (hay còn gọi là trong 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);
})();

Làm thế nào nó hoạt động:

Chrome , Safari và có thể là các trình duyệt webkit khác kích hoạt sự kiện onpopstate khi tài liệu đã được tải. Điều này không có chủ đích, vì vậy chúng tôi chặn các sự kiện cửa sổ bật lên cho đến khi vòng lặp sự kiện đầu tiên hình thành sau khi tải xong tài liệu. Điều này được thực hiện bởi các lệnh gọi PreventDefault và stopIm InstantPropagation (không giống như stopPropagation stopIm InstantPropagation dừng tất cả các lệnh gọi của trình xử lý sự kiện ngay lập tức).

Tuy nhiên, vì readyState của tài liệu đã ở trạng thái "hoàn thành" khi Chrome kích hoạt nhầm onpopstate, chúng tôi cho phép các sự kiện opopstate đã được kích hoạt trước khi quá trình tải tài liệu hoàn tất để cho phép các lệnh gọi onpopstate trước khi tài liệu được tải.

Cập nhật 2014-04-23: Đã sửa lỗi trong đó các sự kiện cửa sổ bật lên đã bị chặn nếu tập lệnh được thực thi sau khi trang được tải.


4
Giải pháp tốt nhất cho đến nay cho tôi. Tôi đã sử dụng phương thức setTimeout trong nhiều năm, nhưng nó gây ra lỗi khi trang tải chậm / khi người dùng kích hoạt pushstate rất nhanh. window.history.statekhông thành công khi tải lại nếu một trạng thái đã được thiết lập. Đặt biến trước mã bộ lộn xộn pushState. Giải pháp của bạn rất thanh lịch và có thể được đặt lên trên các tập lệnh khác mà không ảnh hưởng đến phần còn lại của codebase. Cảm ơn.
kik

4
Đây là giải pháp! Giá như tôi đọc được điều này từ những ngày trước khi cố gắng giải quyết vấn đề này. Đây là chiếc duy nhất hoạt động hoàn hảo. Cảm ơn bạn!
Ben Fleming

1
Giải thích tuyệt vời và giải pháp duy nhất đáng để thử.
Sunny R Gupta

Đây là một giải pháp tuyệt vời. Tại thời điểm viết bài, chrome & FF mới nhất tốt, vấn đề là với safari (mac & iphone). Giải pháp này hoạt động giống như một sự quyến rũ.
Vin

1
Chà! Nếu Safari đang làm hỏng ngày của bạn, đây là những mã bạn đang tìm kiếm!
DanO

28

Chỉ sử dụng setTimeout không phải là một giải pháp chính xác vì bạn không biết mất bao lâu để tải nội dung nên có thể sự kiện popstate được phát ra sau thời gian chờ.

Đây là giải pháp của tôi: 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);
});

Đây là giải pháp duy nhất làm việc cho tôi. Cảm ơn!
Justin

3
ngọt! Hoạt động tốt hơn câu trả lời được ủng hộ nhiều nhất và / hoặc được chấp nhận!
ProblemsOfSumit

Có ai có liên kết hoạt động đến giải pháp này không, tôi không thể truy cập liên kết đã cho gist.github.com/3551566
Harbir

1
tại sao bạn cần ý chính?
Oleg Vaskevich

Câu trả lời hoàn hảo. Cảm ơn.
sideroxylon

25

Giải pháp đã được tìm thấy trong jquery.pjax.js dòng 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
Giải pháp này đã ngừng hoạt động đối với tôi trên Windows (Chrome 19), vẫn hoạt động trên Mac (Chrome 18). Có vẻ như history.state tồn tại trong Chrome 19 nhưng không phải 18.
johnnymire

1
@johnnymire Tôi đã đăng giải pháp của mình cho Chrome 19 thấp hơn: stackoverflow.com/a/10651028/291500
Pavel Linkesch

Ai đó nên chỉnh sửa câu trả lời này để phản ánh hành vi sau Chrome 19 này.
Jorge Suárez de Lis

5
Đối với bất cứ ai tìm kiếm một giải pháp, câu trả lời từ Torben hoạt động giống như một nét duyên dáng trên các phiên bản Chrome và Firefox hiện tại đến ngày hôm nay
Jorge Suárez de Lis

1
Bây giờ vào tháng 4 năm 2015, tôi đã sử dụng giải pháp này để giải quyết sự cố với Safari. Tôi đã phải sử dụng sự kiện onpopstate để đạt được xử lý khi nhấn nút quay lại trên ứng dụng iOS / Android kết hợp bằng Cordova. Safari kích hoạt trạng thái onpopstate của nó ngay cả sau khi tải lại, mà tôi gọi là lập trình. Với cách thiết lập mã của tôi, nó đã đi vào một vòng lặp vô hạn sau khi reload () được gọi. Điều này đã sửa nó. Tôi chỉ cần 3 dòng đầu tiên sau khai báo sự kiện (cộng với các nhiệm vụ ở trên cùng).
Martavis P.

14

Một giải pháp trực tiếp hơn so với việc thực hiện lại pjax là đặt một biến trên pushState và kiểm tra biến trên popState, do đó, popState ban đầu không kích hoạt liên tục khi tải (không phải là giải pháp dành riêng cho jquery, chỉ sử dụng nó cho các sự kiện):

$(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
}

Đó sẽ không phải là nếu luôn luôn đánh giá là sai? Ý tôi là, window.history.readyluôn luôn đúng.
Andriy Drozdyuk,

Đã cập nhật ví dụ để rõ ràng hơn một chút. Về cơ bản, bạn chỉ muốn xử lý các sự kiện popstate một khi bạn đưa ra tất cả rõ ràng. Bạn có thể đạt được hiệu quả tương tự với setTimeout 500ms sau khi tải, nhưng thành thật mà nói thì nó hơi rẻ.
Tom McKenzie

3
Điều này sẽ là câu trả lời IMO, tôi đã cố gắng giải pháp Pavels và nó không hoạt động đúng trong Firefox
Porco

Điều này làm việc cho tôi như một giải pháp dễ dàng cho vấn đề khó chịu này. Cảm ơn rất nhiều!
nirazul

!window.history.ready && !ev.originalEvent.state- hai điều đó không bao giờ được xác định khi tôi làm mới trang. do đó không có mã nào thực thi.
chovy

4

Sự kiện onpopstate ban đầu của Webkit không có trạng thái nào được gán, vì vậy bạn có thể sử dụng sự kiện này để kiểm tra hành vi không mong muốn:

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

Một giải pháp toàn diện, cho phép điều hướng trở lại trang gốc, sẽ được xây dựng dựa trên ý tưởng này:

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

Mặc dù điều này vẫn có thể kích hoạt hai lần (với một số điều hướng tiện lợi), nó có thể được xử lý đơn giản bằng cách kiểm tra so với href trước đó.


1
Cảm ơn. Giải pháp pjax đã ngừng hoạt động trên iOS 5.0 vì giải pháp window.history.statenày cũng không hoạt động trong iOS. Chỉ cần kiểm tra xem thuộc tính e.state là null là đủ.
Husky

Có bất kỳ vấn đề đã biết nào với giải pháp này không? Nó hoạt động hoàn hảo trên bất kỳ trình duyệt nào tôi thử nghiệm.
Jacob Ewing

3

Đây là cách giải quyết của tôi.

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

Không dành cho tôi trên Chromium 28 trên Ubuntu. Đôi khi, sự kiện vẫn bị sa thải.
Jorge Suárez de Lis

Bản thân tôi đã thử điều này, không phải lúc nào cũng hiệu quả. Đôi khi popstate bắn muộn hơn thời gian chờ tùy thuộc vào tải trang ban đầu
Andrew Burgess

1

Đây là giải pháp của tôi:

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

Trong trường hợp sử dụng, việc event.state !== nullquay lại lịch sử về trang được tải đầu tiên sẽ không hoạt động trong các trình duyệt không dành cho thiết bị di động. Tôi sử dụng sessionStorage để đánh dấu thời điểm điều hướng ajax thực sự bắt đầu.

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

Các giải pháp được trình bày có vấn đề khi tải lại trang. Cách sau có vẻ hoạt động tốt hơn, nhưng tôi mới chỉ thử nghiệm Firefox và Chrome. Nó sử dụng thực tế, rằng dường như có sự khác biệt giữa e.event.statewindow.history.state.

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

e.eventlà không xác định
chovy

-1

Tôi biết bạn đã yêu cầu chống lại nó, nhưng bạn thực sự chỉ nên sử dụng History.js vì nó xóa hàng triệu điểm không tương thích của trình duyệt. Tôi đã đi theo con đường sửa chữa thủ công chỉ để sau đó nhận thấy ngày càng có nhiều vấn đề mà bạn sẽ chỉ tìm ra cách trên đường. Nó thực sự không khó bây giờ:

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

Và đọc api tại https://github.com/browserstate/history.js


-1

Điều này giải quyết các vấn đề đối với tôi. Tất cả những gì tôi đã làm là đặt một hàm thời gian chờ làm trì hoãn việc thực thi hàm đủ lâu để bỏ lỡ sự kiện cửa sổ bật lên được kích hoạt trên pageload

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

-2

Bạn có thể tạo sự kiện và kích hoạt sự kiện đó sau trình xử lý tải của bạn.

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

Lưu ý, điều này hơi bị lỗi trong Chrome / Safari, nhưng tôi đã gửi bản vá cho WebKit và nó sẽ sớm có sẵn, nhưng đó là cách "đúng đắn nhất".


-2

Điều này đã làm việc cho tôi trong Firefox và Chrome

window.onpopstate = function(event) { //back button click
    console.log("onpopstate");
    if (event.state) {
        window.location.reload();
    }
};
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.