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


76

我有一个AJAX应用。用户单击一个按钮,页面的显示就会改变。他们单击后退按钮,希望进入原始状态,但转而在浏览器中转到上一页。

如何截取并重新分配后退按钮事件?我已经研究过RSH之类的库(我无法使用...),而且我听说使用井号标签在某种程度上有所帮助,但我无法理解。

Answers:


119

啊,后退按钮。您可能想象“后退”会触发一个JavaScript事件,您可以像这样简单地将其取消:

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

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

如果您确实有一个Web应用程序(而不只是一个具有ajaxy功能的网站),那么接管后退按钮(如您提到的URL上的片段)是合理的。Gmail会这样做。我说的是什么时候您的Web应用程序在一个页面中完全独立。

该技术很简单-每当用户执行修改操作时,都将重定向到您已经使用的相同URL,但具有不同的哈希片段。例如

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

如果您的Web应用程序的整体状态是可哈希的,那么使用哈希的好地方。假设您有一封电子邮件列表,也许您将它们的所有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);

56

要对一个古老(但很受欢迎)的问题给出最新答案:

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

使用history.pushState()更改XMLHttpRequest在状态更改后创建的对象的HTTP标头中使用的引荐来源网址。引荐来源网址是文档的URL,该文档的窗口this位于创建XMLHttpRequest对象时。

来源:从Mozilla开发人员网络处理浏览器历史记录


10

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

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

到目前为止,虽然仅在Google Chrome中进行了测试。

这解决了我的Web应用程序的问题,该应用程序也高度基于AJAX。

也许有点hack-ish,但我将其称为优雅的hacking ;-)每当您尝试向后导航时,它都会在URI中弹出一个哈希部分,从技术上讲,它就是试图向后导航的内容。

它拦截了单击浏览器按钮和鼠标按钮的行为。而且,您也不能通过每秒单击几次来向后暴力破解,这是在基于setTimeout或setInterval的解决方案中会出现的问题。


我认为右键单击“后退”按钮并立即返回两个步骤仍然可行吗?
Hjulle 2014年

9

我非常感谢Darkporter回答中的解释,但是我认为可以通过使用hashchange ”事件来改善它。正如darkporter所解释的那样,您要确保所有按钮都更改window.location.hash值。

  • 一种方法是使用<button>元素,然后将事件附加到元素上,然后再进行设置window.location.hash = "#!somehashstring";
  • 另一种方法是仅对按钮使用链接,例如<a href="#!somehashstring">Button 1</a>。单击这些链接后,哈希将自动更新。

我在井号后加上一个感叹号的原因是为了满足Google的“ hashbang”范式(了解更多信息),如果您希望被搜索引擎索引,这将非常有用。您的哈希字符串通常是名称/值对之类的#!color=blue&shape=triangle或列表之类的#!/blue/triangle-对您的Web应用而言有意义。

然后,您只需要添加这段代码即可,只要哈希值发生变化(包括按下后退按钮,该代码就会触发。似乎没有轮询循环是必要的。

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

我没有在Chrome 36上进行任何测试,但是根据caniuse.com的说法,它应该可以在IE8 +,FF21 +,Chrome21 +和除Opera Mini之外的大多数其他浏览器中使用。


1
只是更新-Google不再推荐“ hashbang”方法,因为他们现在可以成功抓取webmasters.googleblog.com/2015/10/…–
rmir​​abelle

7

禁用浏览器的BACK按钮很容易,就像下面的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;
    }
  });
}

您可以在dotnsfthecssninja中看到它的工作以及更多示例

谢谢 !


1
这是此页面上唯一适用于Chrome v52.0的代码段。谢谢。
Ralpharama

这有效。但是用户可以通过右键单击后退按钮转到上一页。–
Vimal

@VimalS YESSS!这就对了!您必须配置!请阅读我发送的相关页面!历史API中还有更多内容……优点:普通用户知道“点击真”?.. 根据您的需要调整它!
Roberval Sena山本

5

我摸索出了一种覆盖正常的历史行为,创造独特backforward按钮事件,使用HTML5历史API(它不会在IE 9运行)。这是非常棘手的,但是如果您想拦截后退和前进按钮事件并根据需要进行处理,则非常有效。在许多情况下这可能很有用,例如,如果您正在显示远程桌面窗口,并且需要在远程计算机上重现后退和前进按钮的单击,则可能会很有用。

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

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或状态2。 3,然后我们可以确定用户按下的方向。就像这样,我们拦截了一个后退或前进按钮事件。然后,我们根据需要处理它并返回状态2,以便我们可以捕获下一个历史记录导航事件。

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


4

由于隐私问题,无法禁用后退按钮或检查用户的历史记录,但是可以在此历史记录中创建新条目而无需更改页面。每当您的AJAX应用程序更改状态时,请top.location使用新的URI片段更新。

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

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


除了设置top.location外,我还需要处理其他一些无聊的细节吗?
Zack Burt

由于用户单击时不会触发“ onload”事件,因此需要一些东西来触发事件。浏览器之间也存在一些差异。一个好的AJAX库将封装所有这些细节,包括setTimeout()darkporter的示例。
马修2009年

2

我做这样的事情。我与以前的应用程序状态保持数组。

发起为:

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

2
听起来您正在再现后退按钮的功能。这很有趣,但可能不是OP问题的答案。
路加福音

2

在我的情况下,我想防止后退按钮将用户发送到最后一页,而是采取其他措施。通过使用该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事件不会触发。根据您的情况,这也可能适合您。


1

浏览器中的“后退”按钮通常会尝试返回到先前的地址。浏览器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('#', ''))
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.