it-swarm.dev

拦截我的后退按钮的呼叫 AJAX 应用

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

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

71
Zack Burt

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

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

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

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

该技术很简单 - 每当用户采取修改事物的操作时,重定向到您已经使用的相同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);
113
jpsimons

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

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

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

来源: 操纵浏览器历史记录 从Mozilla Developer Network。

48
gitaarik

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

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

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

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

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

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

10
Mark Hünermund Jensen

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

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

我在哈希符号后面有一个感叹号的原因是为了满足谷歌的“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之外的大多数其他浏览器中使用。

8
Luke

很容易禁用 browser 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
Roberval Sena 山本

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

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

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

4
Matthew

在我的情况下,我想阻止后退按钮将用户发送到最后一页,而是采取不同的操作。使用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事件不会触发。根据您的具体情况,这也可能对您有用。

2
Swisher Sweet

我找到了一种方法来覆盖正常的历史行为,并使用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方法,否则就会破坏它。

2
Brian

浏览器中的后退按钮通常会尝试返回上一个地址。 没有本机设备后退按钮按下事件 在浏览器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('#', ''))
}
1
Nainemom

我这样做。我保留了一个以前的app状态的数组。

发起人:

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