在我的 AJAX 应用程序中拦截对后退按钮的调用

Posted

技术标签:

【中文标题】在我的 AJAX 应用程序中拦截对后退按钮的调用【英文标题】:Intercepting call to the back button in my AJAX application 【发布时间】:2010-12-23 02:28:32 【问题描述】:

我有一个 AJAX 应用程序。用户单击一个按钮,页面的显示就会发生变化。他们单击后退按钮,期望回到原始状态,但相反,他们转到浏览器中的上一页。

如何拦截并重新分配返回按钮事件?我研究过像 RSH 之类的库(我无法开始工作......),我听说使用哈希标签会有所帮助,但我无法理解。

【问题讨论】:

【参考方案1】:

使用 jQuery 我做了一个简单的解决方案:

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

不过,目前只在 Google Chrome 中进行了测试。

这解决了我的网络应用程序的问题,它也是高度基于 AJAX 的。

这可能有点 hack'ish - 但我称之为优雅的 hacking ;-) 每当您尝试向后导航时,它会在 URI 中弹出一个哈希部分,从技术上讲,它会尝试向后导航。

它拦截点击浏览器按钮和鼠标按钮。而且你不能通过每秒点击几次来强制它向后,这是在基于 setTimeout 或 setInterval 的解决方案中会出现的问题。

【讨论】:

我认为右键单击后退按钮并一次返回两步仍然有效?【参考方案2】:

浏览器中的后退按钮通常会尝试返回上一个地址。 浏览器 JavaScript 中没有本机设备后退按钮按下事件,但您可以通过使用位置和哈希来更改应用状态来破解它。

想象一个有两个视图的简单应用:

带有获取按钮的默认视图 结果视图

要实现它,请遵循以下示例代码:

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('#', ''))

【讨论】:

【参考方案3】:

禁用浏览器返回按钮非常容易,如下面的JQuery代码:

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

您可以在dotnsf 和thecssninja 看到它的工作原理和更多示例

谢谢!

【讨论】:

这是此页面上唯一在 Chrome v52.0 中对我有用的 sn-p。谢谢。 这行得通。但是用户可以通过右键单击后退按钮转到上一页.. @VimalS 是的!这就对了!你必须配置!请阅读我发送的相关页面!历史 API 中还有更多... 优点:普通用户知道“右击”?...只是我觉得有点跌倒...请。根据您的需要调整它! 这应该是公认的答案。它就像一个魅力!【参考方案4】:

我想出了一种方法来覆盖正常的历史行为并创建不同的 backforward 按钮事件,使用 html5 历史 API(它在 IE 9 中不起作用)。这非常 hacky,但如果您想拦截后退和前进按钮事件并根据需要处理它们,则非常有效。这在许多情况下可能很有用,例如如果您正在显示远程桌面窗口并且需要重现远程计算机上的后退和前进按钮单击。

以下将覆盖后退和前进按钮的行为:

var myHistoryOverride = new HistoryButtonOverride(function()

    console.log("Back Button Pressed");
    return true;
,
function()

    console.log("Forward Button Pressed");
    return true;
);

如果您在其中任何一个回调函数中返回 false,您将允许浏览器继续进行正常的后退/前进操作并离开您的页面。

这是所需的完整脚本:

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

这是一个简单的概念。当我们的页面加载时,我们创建了 3 个不同的历史状态(编号为 1、2 和 3)并将浏览器导航到状态编号 2。因为状态 2 在中间,所以下一个历史导航事件将使我们处于状态 1 或3,由此我们可以确定用户按下了哪个方向。就这样,我们截获了一个后退或前进按钮事件。然后我们随心所欲地处理它并返回到状态号 2,以便我们可以捕获下一个历史导航事件。

显然,在使用 HistoryButtonOverride 脚本时,您需要避免使用 history.replaceState 和 history.pushState 方法,否则您会破坏它。

【讨论】:

【参考方案5】:

在我的情况下,我想阻止后退按钮将用户发送到最后一页,而是采取不同的操作。使用hashchange 事件,我想出了一个适合我的解决方案。此脚本假定您正在使用它的页面尚未使用哈希,就像我的情况一样:

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 = "#";

我在其他一些答案中遇到的问题是hashchange 事件不会触发。根据您的情况,这也可能对您有用。

【讨论】:

【参考方案6】:

要对一个古老(但很流行)的问题提供最新答案:

HTML5 引入了history.pushState()history.replaceState() 方法,分别允许您添加和修改历史条目。这些方法与window.onpopstate 事件结合使用。

使用history.pushState() 会更改在您更改状态后创建的XMLHttpRequest 对象的HTTP 标头中使用的引荐来源网址。引用者将是在创建 XMLHttpRequest 对象时其窗口为 this 的文档的 URL。

来源:Manipulating the browser history 来自 Mozilla 开发者网络。

【讨论】:

【参考方案7】:

我非常感谢darkporter's answer 中给出的解释,但我认为可以通过使用"hashchange" event 来改进它。正如 darkporter 解释的那样,您要确保所有按钮都更改了 window.location.hash 值。

一种方法是使用<button> 元素,然后将事件附加到它们,然后设置window.location.hash = "#!somehashstring";。 另一种方法是只为您的按钮使用链接,例如<a href="#!somehashstring">Button 1</a>。单击这些链接时,哈希值会自动更新。

我在井号后面加一个感叹号的原因是为了满足谷歌的“hashbang”范式(read more about that),如果你想被搜索引擎索引,这很有用。您的哈希字符串通常是名称/值对,例如 #!color=blue&shape=triangle 或类似 #!/blue/triangle 的列表——只要对您的网络应用有意义。

然后你只需要添加这段代码,它会在哈希值改变时触发(包括点击返回按钮时)。似乎不需要轮询循环。

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

我没有在 Chrome 36 上测试过,但是according to caniuse.com,这应该可以在 IE8+、FF21+、Chrome21+ 和除 Opera Mini 之外的大多数其他浏览器中使用。

【讨论】:

只是一个更新 - 谷歌不再推荐“hashbang”方法,因为他们现在能够成功抓取webmasters.googleblog.com/2015/10/…【参考方案8】:

我做这样的事情。我保留了一个包含以前应用程序状态的数组。

发起为:

var backstack = [];

然后我监听位置哈希的变化,当它发生变化时我这样做:

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

这样我就有了一种比较简单的方法来跟踪用户历史记录。现在,如果我想在应用程序中实现一个后退按钮(而不是浏览器按钮),我只需这样做:

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

【讨论】:

听起来您正在重现后退按钮的功能。这很有趣,但可能不是 OP 问题的答案。【参考方案9】:

啊,返回按钮。您可能会想象“返回”会触发一个 javascript 事件,您可以像这样简单地取消它:

document.onHistoryGo = function()  return false; 

不是这样。根本没有这样的事件。

如果您确实有一个 网络应用程序(而不仅仅是一个具有一些 ajaxy 功能的网站),那么接管后退按钮是合理的(正如您提到的那样,URL 上有片段) . Gmail 就是这样做的。我说的是当您的网络应用程序在一个页面中时,所有内容都是独立的。

该技术很简单——每当用户采取修改内容的操作时,重定向到您已经使用的同一个 URL,但使用不同的哈希片段。例如

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

如果您的网络应用的整体状态是可散列的,那么这是使用散列的好地方。假设您有一个电子邮件列表,也许您会将其所有 ID 和已读/未读状态连接起来,并获取一个 MD5 哈希,将其用作您的片段标识符。

这种重定向(仅限哈希)不会尝试从服务器获取任何内容,但会在浏览器的历史列表中插入一个槽。所以在上面的例子中,用户点击“返回”,他们现在在地址栏中显示 #deleted_something。他们再次回击,他们仍然在您的页面上,但没有散列。然后再回来,他们实际上又回到了他们从哪里来的地方。

现在最难的部分是让 JavaScript 检测用户何时回击(这样您就可以恢复状态)。您所要做的就是观察窗口位置并查看它何时发生变化。带投票。 (我知道,糟糕,轮询。嗯,现在没有更好的跨浏览器了)。但是,您将无法判断它们是前进还是后退,因此您必须对散列标识符进行创意。 (也许是与序列号连接的哈希......)

这是代码的要点。 (这也是 jQuery History 插件的工作原理。)

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

【讨论】:

【参考方案10】:

出于隐私考虑,无法禁用后退按钮或检查用户的历史记录,但可以在此历史记录中创建新条目而无需更改页面。每当您的 AJAX 应用程序更改状态时,将 top.location 更新为新的 URI fragment。

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

这将在浏览器的历史堆栈中创建一个新条目。许多 AJAX 库已经处理了所有无聊的细节,例如 Really Simple History。

【讨论】:

除了设置 top.location 之外,我还需要处理哪些无聊的细节? 由于用户点击返回时不会触发“onload”事件,因此您需要一些东西来触发事件。浏览器之间也存在一些差异。一个好的 AJAX 库会封装所有这些细节,包括 darkporter 示例中的 setTimeout()

以上是关于在我的 AJAX 应用程序中拦截对后退按钮的调用的主要内容,如果未能解决你的问题,请参考以下文章

在 Xamarin.Forms UWP 应用程序中拦截鼠标后退按钮

调用 pushViewController 时,后退按钮会覆盖上一个 UIBarButton

vue中通过history api拦截浏览器的前进后退按钮事件

如何处理后退按钮android以退出应用程序并在首页中打开时关闭本机导航侧菜单

对 PHP 函数的 AJAX 调用不会使其在我的数据库中输入值

离子2:如何处理硬件后退按钮,检查应用程序中退出的确认