如何检测浏览器后退按钮事件-跨浏览器


217

您如何确定用户是否按下浏览器中的“后退”按钮?

如何使用#URL系统在单页Web应用程序内强制使用页内后退按钮?

到底为什么浏览器后退按钮不触发自己的事件!?


2
我希望导航事件(后退/前进)会在某个时候添加。到目前为止,这是正在开发中的
llaaalu

Answers:


184

(注意:根据Sharky的反馈,我提供了用于检测退格的代码)

因此,我经常在SO上看到这些问题,并且最近遇到了自己控制后退按钮功能的问题。在为我的应用程序寻找最佳解决方案(带哈希导航的单页)几天后,我提出了一个简单的,跨浏览器,无库的系统来检测后退按钮。

大多数人建议使用:

window.onhashchange = function() {
 //blah blah blah
}

但是,当用户使用更改位置哈希值的页内元素时,也会调用此函数。当用户单击并且页面向后或向前移动时,并不是最佳的用户体验。

为了让您大致了解我的系统,我在用户移动界面时用以前的哈希填充数组。看起来像这样:

function updateHistory(curr) {
    window.location.lasthash.push(window.location.hash);
    window.location.hash = curr;
}

非常简单。我这样做是为了确保跨浏览器支持以及对较旧浏览器的支持。只需将新的哈希传递给函数,它将为您存储它,然后更改哈希(然后将其放入浏览器的历史记录中)。

我还利用了一个页面内后退按钮,该按钮可使用lasthash数组在页面之间移动用户。看起来像这样:

function goBack() {
    window.location.hash = window.location.lasthash[window.location.lasthash.length-1];
    //blah blah blah
    window.location.lasthash.pop();
}

因此,这会将用户移回最后一个哈希,并从数组中删除该最后一个哈希(我现在没有前进按钮)。

所以。如何检测用户是否使用了页面内后退按钮或浏览器按钮?

一开始我看着 window.onbeforeunload,但无济于事-仅在用户要更改页面时才调用。在使用哈希导航的单页应用程序中不会发生这种情况。

因此,经过进一步的挖掘,我看到了有关尝试设置标志变量的建议。在我的案例中,这个问题是我会尝试设置它,但是由于所有内容都是异步的,因此哈希值更改中的if语句不一定总是及时设置。.onMouseDown并非总是在click中被调用,并且将其添加到onclick并不会足够快地触发它。

这是我开始查看document和之间的区别的时候window。我的最终解决方案是使用设置标志document.onmouseover,并使用禁用它document.onmouseleave

发生的情况是,当用户的鼠标位于文档区域内(阅读:呈现的页面,但不包括浏览器框架)时,我的布尔值设置为true。鼠标离开文档区域后,布尔值将翻转为false

这样,我可以将其更改window.onhashchange为:

window.onhashchange = function() {
    if (window.innerDocClick) {
        window.innerDocClick = false;
    } else {
        if (window.location.hash != '#undefined') {
            goBack();
        } else {
            history.pushState("", document.title, window.location.pathname);
            location.reload();
        }
    }
}

您会注意到的支票#undefined。这是因为如果我的数组中没有可用的历史记录,它将返回undefined。我用它来询问用户是否要使用window.onbeforeunload事件离开。

简而言之,对于不一定使用页内后退按钮或数组来存储历史记录的用户:

document.onmouseover = function() {
    //User's mouse is inside the page.
    window.innerDocClick = true;
}

document.onmouseleave = function() {
    //User's mouse has left the page.
    window.innerDocClick = false;
}

window.onhashchange = function() {
    if (window.innerDocClick) {
        //Your own in-page mechanism triggered the hash change
    } else {
        //Browser back button was clicked
    }
}

那里有。一种简单的,由三部分组成的方法,用于检测关于哈希导航的后退按钮使用情况与页内元素的关系。

编辑:

为了确保用户不使用退格键来触发back事件,还可以包括以下内容(感谢@thetoolman 对该问题的回答):

$(function(){
    /*
     * this swallows backspace keys on any non-input element.
     * stops backspace -> back
     */
    var rx = /INPUT|SELECT|TEXTAREA/i;

    $(document).bind("keydown keypress", function(e){
        if( e.which == 8 ){ // 8 == backspace
            if(!rx.test(e.target.tagName) || e.target.disabled || e.target.readOnly ){
                e.preventDefault();
            }
        }
    });
});

5
+1个好主意,但我认为如果用户在鼠标位于浏览器窗口内时使用任何用于“后退”的键盘快捷键(firefox上的退格键),则此操作将失败
Sharky

3
会做的-无论如何我现在在办公室:)(编辑:立即完成)
Xarus

7
那么移动设备(例如ipad)呢
basarat

5
从MAC中的触控板滑动事件怎么样?也可以捕获吗?
Sriganesh Navaneethakrishnan 2015年

6
如何区分前进和后退按钮?
Mr_Perfect 2016年

79

您可以尝试popstate事件处理程序,例如:

window.addEventListener('popstate', function(event) {
    // The popstate event is fired each time when the current history entry changes.

    var r = confirm("You pressed a Back button! Are you sure?!");

    if (r == true) {
        // Call Back button programmatically as per user confirmation.
        history.back();
        // Uncomment below line to redirect to the previous page instead.
        // window.location = document.referrer // Note: IE11 is not supporting this.
    } else {
        // Stay on the current page.
        history.pushState(null, null, window.location.pathname);
    }

    history.pushState(null, null, window.location.pathname);

}, false);

注意:为了获得最佳结果,应仅在要实现逻辑以避免某些其他意外问题的特定页面上加载此代码。

每当当前历史记录条目更改(用户导航到新状态)时,都会触发popstate事件。这发生在浏览器的后退/前进按钮或当用户点击时history.back()history.forward()history.go()以编程方式调用方法。

event.state事件的is属性等于历史状态对象。

对于jQuery语法,将其环绕(在文档准备好后甚至添加侦听器):

(function($) {
  // Above code here.
})(jQuery);

另请参见:页面加载时的window.onpopstate


另请参阅单页应用程序和HTML5 pushState页面上的示例:

<script>
// jQuery
$(window).on('popstate', function (e) {
    var state = e.originalEvent.state;
    if (state !== null) {
        //load content with ajax
    }
});

// Vanilla javascript
window.addEventListener('popstate', function (e) {
    var state = e.state;
    if (state !== null) {
        //load content with ajax
    }
});
</script>

这应该与Chrome 5 +,Firefox 4 +,IE 10 +,Safari 6 +,Opera 11.5+及类似版本兼容。


2
Kenorb,您正确地认为popstate可以抓住更改,但是在以编程方式调用事件和用户单击浏览器按钮之间没有区别。
Xarus

1
关于单页应用程序和HTML5 pushState的精彩文章。非常适合现代的“一页布局”或“单页应用程序”。感谢您的链接和您的回答!
lowtechsun '16

1
e.state在现代浏览器中似乎始终是不确定的
FAjir

我建议您将代码放在一段代码中,以便可以在此处直接运行它。
FourCinnamon0

16

我已经为此要求苦苦挣扎了很长时间,并采用了上面的一些解决方案来实现它。但是,我偶然发现了一个发现,它似乎可以在Chrome,Firefox和Safari浏览器以及Android和iPhone上运行

页面加载:

window.history.pushState({page: 1}, "", "");

window.onpopstate = function(event) {

  // "event" object seems to contain value only when the back button is clicked
  // and if the pop state event fires due to clicks on a button
  // or a link it comes up as "undefined" 

  if(event){
    // Code to handle back button or prevent from navigation
  }
  else{
    // Continue user action through link or button
  }
}

让我知道是否有帮助。如果缺少什么,我将很高兴理解。


1
不对。event即使对于前进按钮也具有价值
Daniel Birowsky Popeski

14

在javascript中,导航类型2表示单击浏览器的后退或前进按钮,并且浏览器实际上是从缓存中获取内容。

if(performance.navigation.type == 2)
{
    //Do your code here
}

1
这在单页面应用程序上不起作用,结果始终为1(表示重新加载)。而且,它并没有使后退和前进按钮之间有所区别,这是非常不幸的。
papillon

“而且,前进和后退按钮之间没有区别,这是非常不幸的。”是的,因为每当从缓存中获取数据时,Performance.navigation.type为2
Hasan Badshah

不能保证在所有浏览器中都可以!因此,您应该编写类似以下内容的代码:if(window.performance){//与性能相关的代码}最好还是使用TYPE_BACK_FORWARD而不是2来提高可读性。
blank94 '19

7

这肯定可以工作(用于检测后退按钮的单击)

$(window).on('popstate', function(event) {
 alert("pop");
});

6

看到这个:

history.pushState(null, null, location.href);
    window.onpopstate = function () {
        history.go(1);
    };

它工作正常...


在Chrome 78.0.3904.70(正式版本)(64位)中
不起作用

确认可在Brave v1.3.118版上工作>>铬:80.0.3987.116(正式版本)(64位)
ZeroThe2nd

如果您来自外部网站,则该页面不起作用。
米兰弗拉赫

5
if (window.performance && window.performance.navigation.type == window.performance.navigation.TYPE_BACK_FORWARD) {
  alert('hello world');
}

这是唯一对我有效的解决方案(它不是一页网站)。它适用于Chrome,Firefox和Safari。


1
window.performance.navigation已被弃用。这是文档developer.mozilla.org/zh-CN/docs/Web/API/Performance/navigation
llaaalu

这在我的.cshtml页面上对我有用。在Chrome和IE上成功运行。谢谢
Justjyde

5

正确答案已经可以回答问题。我想提到新的JavaScript API PerformanceNavigationTiming,它取代了不推荐使用的performance.navigation

如果用户使用“后退”或“前进”按钮登陆您的页面,则以下代码将登录控制台“ back_forward”。在项目中使用兼容性表之前,请先查看一下兼容性表。

var perfEntries = performance.getEntriesByType("navigation");
for (var i = 0; i < perfEntries.length; i++) {
    console.log(perfEntries[i].type);
}

这不是判断此页面上是否单击后退按钮的方法。它告诉您是否在最后一页上单击了它。
Seph Reed

但这恰好是我一直在寻找的实际答案,因为遇到的问题是在我按下“后退”按钮之后,而不是在此之前,这意味着我可以构建一些响应代码来阻止重复的请求问题。谢谢。
安德鲁

1
这可能无法回答问题,但恰恰是我所需要的!好人-我不知道这件事……
霍格斯米尔

4

浏览器: https //jsfiddle.net/Limitlessisa/axt1Lqoz/

对于移动控件:https : //jsfiddle.net/Limitlessisa/axt1Lqoz/show/

$(document).ready(function() {
  $('body').on('click touch', '#share', function(e) {
    $('.share').fadeIn();
  });
});

// geri butonunu yakalama
window.onhashchange = function(e) {
  var oldURL = e.oldURL.split('#')[1];
  var newURL = e.newURL.split('#')[1];

  if (oldURL == 'share') {
    $('.share').fadeOut();
    e.preventDefault();
    return false;
  }
  //console.log('old:'+oldURL+' new:'+newURL);
}
.share{position:fixed; display:none; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,.8); color:white; padding:20px;
<!DOCTYPE html>
<html>

<head>
    <title>Back Button Example</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

</head>

<body style="text-align:center; padding:0;">
    <a href="#share" id="share">Share</a>
    <div class="share" style="">
        <h1>Test Page</h1>
        <p> Back button press please for control.</p>
    </div>
</body>

</html>


这不能回答所问的问题。问题是关于检测使用页面内后退按钮还是浏览器的本机后退按钮。
Xarus '16

2
问题被标记为javascript,而不是jquery。
skwny

3

这是我的看法。假设是,当URL更改但在document检测到的范围内没有单击时,即为浏览器后退(是或前进)。用户单击2秒钟后将重置,以使其在通过Ajax加载内容的页面上起作用:

(function(window, $) {
  var anyClick, consoleLog, debug, delay;
  delay = function(sec, func) {
    return setTimeout(func, sec * 1000);
  };
  debug = true;
  anyClick = false;
  consoleLog = function(type, message) {
    if (debug) {
      return console[type](message);
    }
  };
  $(window.document).click(function() {
    anyClick = true;
    consoleLog("info", "clicked");
    return delay(2, function() {
      consoleLog("info", "reset click state");
      return anyClick = false;
    });
  });
  return window.addEventListener("popstate", function(e) {
    if (anyClick !== true) {
      consoleLog("info", "Back clicked");
      return window.dataLayer.push({
        event: 'analyticsEvent',
        eventCategory: 'test',
        eventAction: 'test'
      });
    }
  });
})(window, jQuery);

2

我能够使用此线程中的某些答案,而其他答案则可使其在IE和Chrome / Edge中正常工作。IE11不支持对我的history.pushState

if (history.pushState) {
    //Chrome and modern browsers
    history.pushState(null, document.title, location.href);
    window.addEventListener('popstate', function (event) {
        history.pushState(null, document.title, location.href);
    });
}
else {
    //IE
    history.forward();
}

刚刚在Chrome 78.0.3904.70(正式版)(64位)上尝试过,但无法正常工作。
Jeffz

2

仅当您重新定义API(更改对象“ history”的方法)时,才能实现完整的组件。我将共享刚刚编写的类。在Chrome和Mozilla上进行了测试,仅支持HTML5和ECMAScript5-6

class HistoryNavigation {
    static init()
    {
        if(HistoryNavigation.is_init===true){
            return;
        }
        HistoryNavigation.is_init=true;

        let history_stack=[];
        let n=0;
        let  current_state={timestamp:Date.now()+n};
        n++;
        let init_HNState;
        if(history.state!==null){
            current_state=history.state.HNState;
            history_stack=history.state.HNState.history_stack;
            init_HNState=history.state.HNState;
        } else {
            init_HNState={timestamp:current_state.timestamp,history_stack};
        }
        let listenerPushState=function(params){
            params=Object.assign({state:null},params);
            params.state=params.state!==null?Object.assign({},params.state):{};
            let h_state={ timestamp:Date.now()+n};
            n++;
            let key = history_stack.indexOf(current_state.timestamp);
            key=key+1;
            history_stack.splice(key);
            history_stack.push(h_state.timestamp);
            h_state.history_stack=history_stack;
            params.state.HNState=h_state;
            current_state=h_state;
            return params;
        };
        let listenerReplaceState=function(params){
            params=Object.assign({state:null},params);
            params.state=params.state!==null?Object.assign({},params.state):null;
            let h_state=Object.assign({},current_state);
            h_state.history_stack=history_stack;
            params.state.HNState=h_state;
            return params;
        };
        let desc=Object.getOwnPropertyDescriptors(History.prototype);
        delete desc.constructor;
        Object.defineProperties(History.prototype,{

            replaceState:Object.assign({},desc.replaceState,{
                value:function(state,title,url){
                    let params={state,title,url};
                    HistoryNavigation.dispatchEvent('history.state.replace',params);
                    params=Object.assign({state,title,url},params);
                    params=listenerReplaceState(params);
                    desc.replaceState.value.call(this,params.state,params.title,params.url);
                }
            }),
            pushState:Object.assign({},desc.pushState,{
                value:function(state,title,url){
                    let params={state,title,url};
                    HistoryNavigation.dispatchEvent('history.state.push',params);
                    params=Object.assign({state,title,url},params);
                    params=listenerPushState(params);
                    return desc.pushState.value.call(this, params.state, params.title, params.url);
                }
            })
        });
        HistoryNavigation.addEventListener('popstate',function(event){
            let HNState;
            if(event.state==null){
                HNState=init_HNState;
            } else {
                HNState=event.state.HNState;
            }
            let key_prev=history_stack.indexOf(current_state.timestamp);
            let key_state=history_stack.indexOf(HNState.timestamp);
            let delta=key_state-key_prev;
            let params={delta,event,state:Object.assign({},event.state)};
            delete params.state.HNState;
            HNState.history_stack=history_stack;
            if(event.state!==null){
                event.state.HNState=HNState;
            }
            current_state=HNState;
            HistoryNavigation.dispatchEvent('history.go',params);
        });

    }
    static addEventListener(...arg)
    {
        window.addEventListener(...arg);
    }
    static removeEventListener(...arg)
    {
        window.removeEventListener(...arg);
    }
    static dispatchEvent(event,params)
    {
        if(!(event instanceof Event)){
            event=new Event(event,{cancelable:true});
        }
        event.params=params;
        window.dispatchEvent(event);
    };
}
HistoryNavigation.init();

// exemple

HistoryNavigation.addEventListener('popstate',function(event){
    console.log('Will not start because they blocked the work');
});
HistoryNavigation.addEventListener('history.go',function(event){
    event.params.event.stopImmediatePropagation();// blocked popstate listeners
    console.log(event.params);
    // back or forward - see event.params.delta

});
HistoryNavigation.addEventListener('history.state.push',function(event){
    console.log(event);
});
HistoryNavigation.addEventListener('history.state.replace',function(event){
    console.log(event);
});
history.pushState({h:'hello'},'','');
history.pushState({h:'hello2'},'','');
history.pushState({h:'hello3'},'','');
history.back();

    ```

不错的方法!感谢您添加此内容(这么多年后,我仍然感到很惊讶,关于该线程的对话)
Xarus

这似乎是一个聪明的历史跟踪机制。但是,该代码没有注释,因此很难遵循。如果有人离开当前页面,该代码将不会检测到该按钮的按下,从而无法回答原始问题“您如何确定地检测用户是否已按下浏览器中的后退按钮?” 按钮的按下和历史记录的跟踪是两个不同的事物,即使其中一个可能触发另一个。仍然存在对话的事实表明Web浏览器设计存在重大缺陷。
CubicleSoft

1

document.mouseover不适用于IE和FireFox。但是我已经试过了:

$(document).ready(function () {
  setInterval(function () {
    var $sample = $("body");
    if ($sample.is(":hover")) {
      window.innerDocClick = true;
    } else {
      window.innerDocClick = false;
    }
  });

});

window.onhashchange = function () {
  if (window.innerDocClick) {
    //Your own in-page mechanism triggered the hash change
  } else {
    //Browser back or forward button was pressed
  }
};

这适用于Chrome和IE,不适用于FireFox。仍在努力使FireFox正确。欢迎使用任何简单的方法来检测浏览器的后退/前进按钮单击,尤其是在JQuery中,尤其在AngularJS或纯Javascript中。


1
 <input style="display:none" id="__pageLoaded" value=""/>


 $(document).ready(function () {
        if ($("#__pageLoaded").val() != 1) {

            $("#__pageLoaded").val(1);


        } else {
            shared.isBackLoad = true;
            $("#__pageLoaded").val(1);  

            // Call any function that handles your back event

        }
    });

上面的代码为我工作。在移动浏览器上,当用户单击“后退”按钮时,我们希望恢复其上次访问时的页面状态。


上面的代码对我来说很有效,在移动浏览器上,当用户单击“后退”按钮时,我们希望恢复其上次访问时的页面状态
Ashwin,

0

我通过跟踪触发该事件的原始事件hashchange(无论是滑动,点击还是滚轮)来解决该问题,以使该事件不会被误认为是简单的页面登陆,并在我的每个事件绑定。false点击后退按钮时,浏览器不会再次将标志设置为:

var evt = null,
canGoBackToThePast = true;

$('#next-slide').on('click touch', function(e) {
    evt = e;
    canGobackToThePast = false;
    // your logic (remember to set the 'canGoBackToThePast' flag back to 'true' at the end of it)
}

-21

我尝试了上述选项,但没有一个对我有用。这是解决方案

if(window.event)
   {
        if(window.event.clientX < 40 && window.event.clientY < 0)
        {
            alert("Browser back button is clicked...");
        }
        else
        {
            alert("Browser refresh button is clicked...");
        }
    }

有关更多详细信息,请参考此链接http://www.codeproject.com/Articles/696526/Solution-to-Browser-Back-Button-Click-Event-Handli


8
@MunamYousuf很明显,为什么这段代码没有真正起作用。它跟踪浏览器的后退按钮,但该按钮在视口之外,因此不应获得坐标clientX和clientY。假设它可以工作,那么iOS呢?“后退”按钮位于底部...同样,其编写方式效率极低,这是触发每个事件的条件。
黄沾

这太可怕了!你为什么要这样做?:/而且它甚至无法像James所说的那样工作。
msqar
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.