Chặn cuộc gọi đến nút quay lại trong ứng dụng AJAX của tôi


76

Tôi có một ứng dụng AJAX. Người dùng nhấp vào một nút và hiển thị của trang sẽ thay đổi. Họ nhấp vào nút quay lại, mong muốn chuyển về trạng thái ban đầu, nhưng thay vào đó, họ chuyển đến trang trước trong trình duyệt của mình.

Làm cách nào để chặn và chỉ định lại sự kiện nút quay lại? Tôi đã xem xét các thư viện như RSH (mà tôi không thể làm việc ...) và tôi nghe nói rằng việc sử dụng thẻ băm bằng cách nào đó sẽ hữu ích, nhưng tôi không hiểu được.

Câu trả lời:


120

À, nút quay lại. Bạn có thể tưởng tượng "back" kích hoạt một sự kiện JavaScript mà bạn có thể chỉ cần hủy như vậy:

document.onHistoryGo = function() { return false; }

Không phải vậy. Đơn giản là không có sự kiện nào như vậy.

Nếu bạn thực sự có một ứng dụng web (thay vì chỉ một trang web với một số tính năng ajaxy) thì việc sử dụng nút quay lại (với các đoạn trên URL, như bạn đã đề cập) là hợp lý. Gmail thực hiện điều này. Tôi đang nói về thời điểm ứng dụng web của bạn trong một trang, tất cả đều độc lập.

Kỹ thuật này rất đơn giản - bất cứ khi nào người dùng thực hiện hành động sửa đổi mọi thứ, hãy chuyển hướng đến cùng một URL mà bạn đã truy cập, nhưng với một đoạn băm khác. Ví dụ

window.location.hash = "#deleted_something";
...
window.location.hash = "#did_something_else";

Nếu trạng thái tổng thể của ứng dụng web của bạn có thể được băm, đây là một nơi tuyệt vời để sử dụng hàm băm. Giả sử bạn có một danh sách các email, có thể bạn sẽ nối tất cả các ID của chúng và các trạng thái đã đọc / chưa đọc và lấy một mã băm MD5, sử dụng nó làm mã nhận dạng phân đoạn của bạn.

Loại chuyển hướng này (chỉ băm) không cố gắng tìm nạp bất kỳ thứ gì từ máy chủ, nhưng nó chèn một vị trí trong danh sách lịch sử của trình duyệt. Vì vậy, trong ví dụ trên, người dùng nhấn "back" và họ hiện đang hiển thị #deleted_something trên thanh địa chỉ. Họ đánh lại một lần nữa và họ vẫn ở trên trang của bạn nhưng không có băm. Sau đó quay lại lần nữa và họ thực sự quay trở lại, đến bất cứ nơi nào họ đến.

Tuy nhiên, bây giờ là phần khó, việc JavaScript của bạn phát hiện khi nào người dùng đánh trả (để bạn có thể hoàn nguyên trạng thái). Tất cả những gì bạn làm là xem vị trí cửa sổ và xem khi nào nó thay đổi. Với thăm dò ý kiến. (Tôi biết, yuck, thăm dò ý kiến. Chà, không có gì tốt hơn trình duyệt chéo ngay bây giờ). Mặc dù vậy, bạn sẽ không thể biết họ tiến hay lùi, vì vậy bạn sẽ phải sáng tạo với số nhận dạng băm của mình. (Có lẽ một băm được nối với một số thứ tự ...)

Đây là ý chính của mã. (Đây cũng là cách plugin Lịch sử jQuery hoạt động.)

var hash = window.location.hash;
setInterval(function(){
    if (window.location.hash != hash) {
        hash = window.location.hash;
        alert("User went back or forward to application state represented by " + hash);
    }
}, 100);

57

Để đưa ra câu trả lời cập nhật cho một câu hỏi cũ (nhưng phổ biến):

HTML5 đã giới thiệu các phương thức history.pushState()history.replaceState(), cho phép bạn thêm và sửa đổi các mục lịch sử tương ứng. Các phương pháp này hoạt động cùng với window.onpopstatesự kiện.

Sử dụng các history.pushState()thay đổi liên kết giới thiệu được sử dụng trong tiêu đề HTTP cho XMLHttpRequestcác đối tượng được tạo sau khi bạn thay đổi trạng thái. Liên kết giới thiệu sẽ là URL của tài liệu có cửa sổ thistại thời điểm tạo XMLHttpRequestđối tượng.

Nguồn: Thao tác lịch sử trình duyệt từ Mạng nhà phát triển Mozilla.


10

Sử dụng jQuery, tôi đã tạo ra một giải pháp đơn giản:

$(window).on('hashchange', function() {
    top.location = '#main';
    // Eventually alert the user describing what happened
});

Cho đến nay chỉ được thử nghiệm trong Google Chrome.

Điều này đã giải quyết vấn đề cho ứng dụng web của tôi cũng dựa trên AJAX.

Nó có lẽ là một chút hack'ish - nhưng tôi sẽ gọi nó là hack thanh lịch ;-) Bất cứ khi nào bạn cố gắng điều hướng ngược lại, nó sẽ bật ra một phần băm trong URI về mặt kỹ thuật, nó sẽ cố gắng điều hướng ngược lại.

Nó chặn cả việc nhấp vào nút trình duyệt và nút chuột. Và bạn cũng không thể ép buộc nó ngược lại bằng cách nhấp vài lần trong một giây, đây là một vấn đề sẽ xảy ra trong các giải pháp dựa trên setTimeout hoặc setInterval.


Tôi giả sử rằng nhấp chuột phải vào nút quay lại và quay lại hai bước cùng một lúc vẫn hoạt động?
Hjulle

9

Tôi thực sự đánh giá cao lời giải thích được đưa ra trong câu trả lời của darkporter , nhưng tôi nghĩ rằng nó có thể được cải thiện bằng cách sử dụng một " hashchange " sự kiện . Như darkporter đã giải thích, bạn muốn đảm bảo rằng tất cả các nút của mình đều thay đổi giá trị window.location.hash.

  • Một cách là sử dụng <button>các phần tử, sau đó đính kèm các sự kiện vào chúng rồi thiết lập window.location.hash = "#!somehashstring";.
  • Một cách khác để làm điều này là chỉ sử dụng các liên kết cho các nút của bạn, chẳng hạn như <a href="#!somehashstring">Button 1</a>. Hàm băm được cập nhật tự động khi các liên kết này được nhấp vào.

Lý do tôi có dấu chấm than sau dấu thăng là để đáp ứng mô hình "hashbang" của Google ( đọc thêm về điều đó ), rất hữu ích nếu bạn muốn được lập chỉ mục bởi các công cụ tìm kiếm. Chuỗi băm của bạn thường sẽ là các cặp tên / giá trị như #!color=blue&shape=trianglehoặc một danh sách giống như #!/blue/triangle- bất kỳ điều gì có ý nghĩa đối với ứng dụng web của bạn.

Sau đó, bạn chỉ cần thêm bit mã này sẽ kích hoạt bất cứ khi nào giá trị băm thay đổi (bao gồm khi nhấn nút quay lại ). Không có vòng lặp bỏ phiếu dường như là cần thiết.

window.addEventListener("hashchange", function(){
    console.log("Hash changed to", window.location.hash);
    // .... Do your thing here...
});

Tôi chưa thử nghiệm bất kỳ thứ gì ngoài Chrome 36, nhưng theo caniuse.com , điều này sẽ khả dụng trong IE8 +, FF21 +, Chrome21 + và hầu hết các trình duyệt khác ngoại trừ Opera Mini.


1
Chỉ cần một bản cập nhật - google không còn khuyến cáo các "hashbang" cách tiếp cận vì họ là thành công có thể crawl tại webmasters.googleblog.com/2015/10/...
rmirabelle

7

Rất dễ dàng để tắt nút QUAY LẠI của trình duyệt , như mã JQuery bên dưới:

// History API 
if( window.history && window.history.pushState ){

  history.pushState( "nohb", null, "" );
  $(window).on( "popstate", function(event){
    if( !event.originalEvent.state ){
      history.pushState( "nohb", null, "" );
      return;
    }
  });
}

Bạn có thể thấy nó hoạt động và các ví dụ khác ở đây dotnsf và ở đây thecssninja

Cảm ơn !


1
Đây là đoạn mã duy nhất trên trang này phù hợp với tôi trong Chrome v52.0. Cảm ơn.
Ralpharama 09/09/16

điều này hoạt động. nhưng người dùng có thể truy cập trang trước bằng cách nhấp chuột phải vào nút quay lại ..
Vimal

@VimalS YESSS! đó là nó! bạn phải cấu hình! vui lòng đọc các trang liên kết mà tôi đã gửi! còn nhiều hơn thế nữa trong lịch sử API ... xin vui lòng: người dùng thông thường biết "lần nhấp nghiêm trọng"? .. chỉ là một chút thất bại tôi mày mò ... XIN VUI LÒNG. điều này phù hợp với nhu cầu của bạn!
Roberval Sena 山 本

5

Tôi đã làm việc ra một cách để thay đổi hành vi lịch sử bình thường và tạo ra khác biệt backforward các sự kiện nút, sử dụng API lịch sử HTML5 (nó sẽ không hoạt động trong IE 9). Điều này rất khó, nhưng hiệu quả nếu bạn muốn chặn các sự kiện nút quay lại và chuyển tiếp và xử lý chúng theo cách bạn muốn. Điều này có thể hữu ích trong một số trường hợp, ví dụ: nếu bạn đang hiển thị một cửa sổ màn hình từ xa và cần tạo lại các lần nhấp vào nút quay lại và chuyển tiếp trên máy tính từ xa.

Điều sau sẽ ghi đè hành vi của nút quay lại và chuyển tiếp:

var myHistoryOverride = new HistoryButtonOverride(function()
{
    console.log("Back Button Pressed");
    return true;
},
function()
{
    console.log("Forward Button Pressed");
    return true;
});

Nếu bạn trả về false trong một trong hai hàm gọi lại đó, bạn sẽ cho phép trình duyệt tiếp tục hoạt động quay lại / chuyển tiếp bình thường và rời khỏi trang của bạn.

Đây là tập lệnh đầy đủ được yêu cầu:

function HistoryButtonOverride(BackButtonPressed, ForwardButtonPressed)
{
    var Reset = function ()
    {
        if (history.state == null)
            return;
        if (history.state.customHistoryStage == 1)
            history.forward();
        else if (history.state.customHistoryStage == 3)
            history.back();
    }
    var BuildURLWithHash = function ()
    {
        // The URLs of our 3 history states must have hash strings in them so that back and forward events never cause a page reload.
        return location.origin + location.pathname + location.search + (location.hash && location.hash.length > 1 ? location.hash : "#");
    }
    if (history.state == null)
    {
        // This is the first page load. Inject new history states to help identify back/forward button presses.
        var initialHistoryLength = history.length;
        history.replaceState({ customHistoryStage: 1, initialHistoryLength: initialHistoryLength }, "", BuildURLWithHash());
        history.pushState({ customHistoryStage: 2, initialHistoryLength: initialHistoryLength }, "", BuildURLWithHash());
        history.pushState({ customHistoryStage: 3, initialHistoryLength: initialHistoryLength }, "", BuildURLWithHash());
        history.back();
    }
    else if (history.state.customHistoryStage == 1)
        history.forward();
    else if (history.state.customHistoryStage == 3)
        history.back();

    $(window).bind("popstate", function ()
    {
        // Called when history navigation occurs.
        if (history.state == null)
            return;
        if (history.state.customHistoryStage == 1)
        {
            if (typeof BackButtonPressed == "function" && BackButtonPressed())
            {
                Reset();
                return;
            }
            if (history.state.initialHistoryLength > 1)
                history.back(); // There is back-history to go to.
            else
                history.forward(); // No back-history to go to, so undo the back operation.
        }
        else if (history.state.customHistoryStage == 3)
        {
            if (typeof ForwardButtonPressed == "function" && ForwardButtonPressed())
            {
                Reset();
                return;
            }
            if (history.length > history.state.initialHistoryLength + 2)
                history.forward(); // There is forward-history to go to.
            else
                history.back(); // No forward-history to go to, so undo the forward operation.
        }
    });
};

Điều này hoạt động theo một khái niệm đơn giản. Khi trang của chúng tôi tải, chúng tôi tạo 3 trạng thái lịch sử riêng biệt (được đánh số 1, 2 và 3) và điều hướng trình duyệt đến trạng thái số 2. Vì trạng thái 2 nằm ở giữa, sự kiện điều hướng lịch sử tiếp theo sẽ đưa chúng ta vào trạng thái 1 hoặc 3, và từ đó chúng tôi có thể xác định hướng người dùng nhấn. Và giống như vậy, chúng tôi đã chặn một sự kiện nút quay lại hoặc chuyển tiếp. Sau đó, chúng tôi xử lý nó theo cách chúng tôi muốn và quay lại trạng thái số 2 để chúng tôi có thể nắm bắt sự kiện điều hướng lịch sử tiếp theo.

Rõ ràng, bạn cần hạn chế sử dụng các phương thức history.replaceState và history.pushState trong khi sử dụng tập lệnh HistoryButtonOverride, nếu không bạn sẽ phá vỡ nó.


4

Do các lo ngại về quyền riêng tư, không thể tắt nút quay lại hoặc kiểm tra lịch sử của người dùng, nhưng có thể tạo các mục mới trong lịch sử này mà không cần thay đổi trang. Bất cứ khi nào ứng dụng AJAX của bạn thay đổi trạng thái, hãy cập nhật top.locationbằng một phân đoạn URI mới .

top.location = "#new-application-state";

Thao tác này sẽ tạo một mục mới trong ngăn xếp lịch sử của trình duyệt. Một số thư viện AJAX đã xử lý tất cả các chi tiết nhàm chán, chẳng hạn như Lịch sử Thực sự Đơn giản .


Tôi cần quan tâm đến những chi tiết nhàm chán nào khác ngoài việc thiết lập top.location?
Zack Burt

Vì sự kiện "onload" không được kích hoạt khi người dùng nhấp lại, bạn cần một thứ gì đó để kích hoạt sự kiện. Cũng có một số khác biệt giữa các trình duyệt. Một thư viện AJAX tốt sẽ đóng gói tất cả các chi tiết này, bao gồm cả setTimeout()ví dụ trong darkporter.
Matthew

2

Tôi làm một cái gì đó như thế này. Tôi giữ một mảng với các trạng thái ứng dụng trước đó.

Bắt đầu là:

var backstack = [];

Sau đó, tôi lắng nghe những thay đổi trong băm vị trí và khi nó thay đổi, tôi thực hiện điều này:

if (backstack.length > 1 && backstack[backstack.length - 2] == newHash) {
    // Back button was probably pressed
    backstack.pop();
} else
    backstack.push(newHash);

Bằng cách này, tôi có một cách hơi đơn giản để theo dõi lịch sử người dùng. Bây giờ nếu tôi muốn triển khai nút quay lại trong ứng dụng (không phải đáy trình duyệt), tôi chỉ cần thực hiện:

window.location.hash = backstack.pop();

2
Có vẻ như bạn đang tái tạo chức năng của nút quay lại. Điều đó thật thú vị, nhưng có lẽ không phải là câu trả lời cho câu hỏi của OP.
Luke

2

Trong tình huống của tôi, tôi muốn ngăn nút quay lại đưa người dùng đến trang cuối cùng và thay vào đó, thực hiện một hành động khác. Sử dụng hashchangesự kiện, tôi đã đưa ra một giải pháp phù hợp với tôi. Tập lệnh này giả sử trang bạn đang sử dụng chưa sử dụng hàm băm, như trong trường hợp của tôi:

var hashChangedCount = -2;
$(window).on("hashchange", function () {
    hashChangedCount++;
    if (top.location.hash == "#main" && hashChangedCount > 0) {
        console.log("Ah ah ah! You didn't say the magic word!");
    }
    top.location.hash = '#main';
});
top.location.hash = "#";

Vấn đề tôi đang gặp phải với một số câu trả lời khác là hashchangesự kiện sẽ không kích hoạt. Tùy thuộc vào tình huống của bạn, điều này cũng có thể phù hợp với bạn.


1

Nút quay lại trong trình duyệt thường cố gắng quay lại địa chỉ trước đó. không có sự kiện nhấn nút quay lại thiết bị gốc trong javascript của trình duyệt, nhưng bạn có thể hack nó bằng cách chơi với vị trí và băm để thay đổi trạng thái ứng dụng của bạn.

hãy tưởng tượng ứng dụng đơn giản với hai chế độ xem:

  • chế độ xem mặc định với nút tìm nạp
  • xem kết quả

để triển khai nó, hãy làm theo mã ví dụ sau:

function goToView (viewName) {
  // before you want to change the view, you have to change window.location.hash.
  // it allows you to go back to the previous view with the back button.
  // so use this function to change your view instead of directly do the job.
  // page parameter is your key to understand what view must be load after this.

  window.location.hash = page
}

function loadView (viewName) {
    // change dom here based on viewName, for example:

    switch (viewName) {
      case 'index':
        document.getElementById('result').style.display = 'none'
        document.getElementById('index').style.display = 'block'
        break
      case 'result':
        document.getElementById('index').style.display = 'none'
        document.getElementById('result').style.display = 'block'
        break
      default:
        document.write('404')
    }
}

window.addEventListener('hashchange', (event) => {
  // oh, the hash is changed! it means we should do our job.
  const viewName = window.location.hash.replace('#', '') || 'index'

  // load that view
  loadView(viewName)
})


// load requested view at start.
if (!window.location.hash) {
  // go to default view if there is no request.
  goToView('index')
} else {
  // load requested view.
  loadView(window.location.hash.replace('#', ''))
}
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.