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

时间:2009-12-04 02:24:35

标签: javascript ajax cross-browser browser-history

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

如何拦截并重新分配后退按钮事件?我已经查看了像RSH这样的库(我无法工作......),我听说使用哈希标签会有所帮助,但我无法理解它。

10 个答案:

答案 0 :(得分:114)

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

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

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

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

该技术很简单 - 只要用户采取修改内容的操作,就会重定向到您已经使用的相同URL,但使用不同的哈希片段。 E.g。

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

答案 1 :(得分:48)

提供旧的(但受欢迎的)问题的最新答案:

  

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

     

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

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

答案 2 :(得分:10)

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

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

到目前为止,仅在Google Chrome中测试过。

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

这可能是一个小黑客 - 但我称之为优雅的黑客;-)每当你尝试向后导航时,它会在URI中弹出一个哈希部分,从技术上来说它会尝试向后导航。< / p>

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

答案 3 :(得分:8)

我非常感谢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以外的大多数其他浏览器。

答案 4 :(得分:5)

很容易禁用浏览器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

看到它的工作和更多示例

谢谢!

答案 5 :(得分:4)

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

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

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

答案 6 :(得分: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事件不会触发。根据您的具体情况,这也适用于您。

答案 7 :(得分:2)

我找到了一种方法来覆盖正常的历史记录行为,并使用HTML5历史记录API创建了不同的backforward按钮事件(它在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方法,否则您就会破坏它。

答案 8 :(得分: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('#', ''))
}

答案 9 :(得分:0)

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

发起人:

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