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

Posted

技术标签:

【中文标题】如何检测浏览器后退按钮事件 - 跨浏览器【英文标题】:How to Detect Browser Back Button event - Cross Browser 【发布时间】:2014-11-06 12:48:29 【问题描述】:

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

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

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

【问题讨论】:

我希望导航事件(后退/前进)能在某个时候添加。到目前为止,这是在工作developer.mozilla.org/en-US/docs/Web/API/… 请支持ongoback活动的提案:discourse.wicg.io/t/set-back-button-url-in-pwas/4112 【参考方案1】:

(注意:根据 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 不会足够快地触发它。

这时我开始研究documentwindow 之间的区别。我的最终解决方案是使用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
    

你有它。一种简单的三部分方法,用于检测与哈希导航相关的后退按钮使用与页内元素。

编辑:

为确保用户不使用退格键触发返回事件,您还可以包括以下内容(感谢@thetoolman on this Question):

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

【讨论】:

+1 好主意,但我认为如果用户在鼠标位于浏览器窗口内时使用任何键盘快捷键用于“返回”(firefox 上的退格键),这将失败 会做的-反正我现在在办公室:)(编辑:现在完成) 移动设备(例如 ipad)呢 MAC 中触控板的滑动事件怎么样。也能拍到吗? 如何区分前进和后退按钮?【参考方案2】:

您可以尝试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 是事件的属性等于历史状态对象。

对于 jQuery 语法,将其包裹起来(在文档准备好后添加监听器):

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

另见:window.onpopstate on page load


另请参阅Single-Page Apps and 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+ 等兼容。

【讨论】:

Kenorb,你说得对,popstate 可以抓取更改,但它并没有区分以编程方式调用事件和用户单击浏览器按钮的时间。 Single-Page Apps and HTML5 pushState 上的精彩帖子。非常适合现代“单页布局”或“单页应用程序”。感谢您的链接和您的回答! e.state 在现代浏览器中似乎总是未定义 我建议你把你的代码放在一个sn-p中,这样你就可以在这里直接运行了。 当在同一文档的两个历史条目之间导航时,popstate 事件仅通过执行浏览器操作触发,例如单击后退按钮(或在 JavaScript 中调用 history.back())。 (developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/…)【参考方案3】:

我已经为这个要求苦苦挣扎了很长一段时间,并采用了上面的一些解决方案来实现它。然而,我偶然发现了一个观察结果,它似乎适用于 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
  

如果这有帮助,请告诉我。如果遗漏了什么,我会很乐意理解。

【讨论】:

不正确。 event 甚至对于前进按钮也有价值【参考方案4】:

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

if(performance.navigation.type == 2)

    //Do your code here

【讨论】:

这不适用于单页应用程序,结果始终为 1(表示重新加载)。而且它并没有区分后退和前进按钮,这是非常不幸的。 " 此外,它并没有区分后退和前进按钮,这是非常不幸的。" 是的,因为每当从缓存中获取数据时,performance.navigation.type 为 2 ... 现在已弃用。见:developer.mozilla.org/en-US/docs/Web/API/Performance/navigation 这在弃用后有效,我不确定跨浏览器兼容性:if (String(window.performance.getEntriesByType("navigation")[0].type) === "back_forward" ) // 在这里做你的代码 我不在乎。它对我有用,很好! 2020 年 12 月【参考方案5】:
if (window.performance && window.performance.navigation.type == window.performance.navigation.TYPE_BACK_FORWARD) 
  alert('hello world');

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

【讨论】:

window.performance.navigation 已被弃用。这是文档developer.mozilla.org/en-US/docs/Web/API/Performance/navigation 这在我的 .cshtml 页面上对我有用。在 Chrome 和 IE 上成功运行。谢谢 @llaaalu 这在弃用后有效,我不确定跨浏览器兼容性: if (String(window.performance.getEntriesByType("navigation")[0].type) === " back_forward") // 在这里做你的代码 【参考方案6】:

已经有正确答案可以回答问题。我想提一下新的 JavaScript API PerformanceNavigationTiming,它正在取代已弃用的 performance.navigation。

如果用户使用后退或前进按钮登陆您的页面,以下代码将登录控制台“back_forward”。 Take a look at compatibility table before using it in your project.

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

【讨论】:

这不是判断返回按钮是否在此页面上被点击的方法。它告诉它是否在最后一页被点击 但这恰好是我正在寻找的实际答案,因为我得到的问题是在我点击后退按钮之后而不是在这意味着我可以构建一些响应代码来阻止我的重复请求问题。谢谢。 这可能无法回答问题,但正是我所需要的!不错的一个 - 我不知道这个......【参考方案7】:

这肯定会起作用(用于检测后退按钮点击)

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

【讨论】:

【参考方案8】:

浏览器: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);
.shareposition: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>

【讨论】:

这没有回答所提出的问题。问题是关于检测页面内后退按钮与浏览器本机后退按钮的使用情况。 问题被标记为 javascript,而不是 jquery。【参考方案9】:

看这个:

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

效果很好……

【讨论】:

不适用于 Chrome 78.0.3904.70(官方版本)(64 位) 确认正在开发 Brave v1.3.118 >> Chromium:80.0.3987.116(官方版本)(64 位) 如果您是从外部站点访问该页面,则无法使用。【参考方案10】:

我的变种:

const inFromBack = performance && performance.getEntriesByType( 'navigation' ).map( nav => nav.type ).includes( 'back_forward' )

【讨论】:

这对我有帮助。谢谢!【参考方案11】:

我能够使用此线程中的一些答案和其他答案使其在 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 (Official Build) (64-bit) 上尝试过,但没有成功。【参考方案12】:

只有重新定义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();

    ```

【讨论】:

不错的方法!感谢您添加此内容(我仍然觉得在这么多年后在此线程上进行对话令人惊讶) 这似乎是一个聪明的历史跟踪机制。但是,代码没有注释,所以很难理解。如果有人离开当前页面,代码将不会检测到按钮按下,这没有回答最初的问题“你如何确定用户是否按下了浏览器中的后退按钮?”按钮按下和历史跟踪是两件不同的事情,尽管其中一个可能会触发另一个。仍有对话的事实表明网络浏览器设计存在重大缺陷。 CubicleSoft,让我们假设有一个实际的过渡(点击链接时直接形成的页面)和虚拟的过渡(由事件生成的页面,而没有真正的点击通过)。在实际转换中,(如果转换期间域不同),由于安全策略和隐私,浏览器不允许您跟踪转换。通过动态转换,您的代码在您的操作上下文中执行,您有机会在导航面板中跟踪操作。至少,这对您的 SPA 应用程序来说是令人满意的。【参考方案13】:

这是我的看法。假设是,当 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);

【讨论】:

【参考方案14】:

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 中也是如此。

【讨论】:

【参考方案15】:

我通过跟踪触发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)

【讨论】:

【参考方案16】:
 <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

        
    );

上面的代码对我有用。在移动浏览器上,当用户点击后退按钮时,我们希望恢复他之前访问的页面状态。

【讨论】:

上面的代码对我有用,在移动浏览器上,当用户点击后退按钮时,我们想恢复他之前访问的页面状态 没有理由将状态存储在 DOM 中。您也不应该重新查询 DOM 以获取输入,它应该存储为局部变量。如果您不想显示&lt;input&gt;,请使用&lt;input type="hidden"&gt;。如果你想使用这种方法,window 上的变量将是首选,但即使这样也不明智。【参考方案17】:

Kotlin/JS (React) 的解决方案:

import org.w3c.dom.events.Event
import kotlin.browser.document
import kotlin.browser.window

...
override fun componentDidMount() 
    window.history.pushState(null, document.title, window.location.href)
    window.addEventListener("popstate", actionHandler)

...
val actionHandler: (Event?) -> Unit = 
    window.history.pushState(
        null,
        document.title,
        window.location.href
    )
    // add your actions here

【讨论】:

【参考方案18】:

正在寻找此问题的解决方案,并根据此处的一些答案和 History.pushState()WindowEventHandlers.onpopstate 的 MDN Web 文档页面整理了一个简单的框架测试 html。

以下 HTML 和 JavaScript 很容易复制、粘贴和测试。

与后退和前进浏览器按钮、快捷键一起使用,添加对 URL 的更改(这在某些情况下很重要)。

足以添加到现有的代码关键点,也应该是可扩展的。

<html>
<body>
<div id="p1">Option 1</div>
<div id="p2">Option 2</div>
<div id="p3">Option 3</div>
<div id="p4">Option 4</div>
<div id="c"></div>
<script>
var chg=
    set:function(str)
        var d=document.getElementById("c");
        d.textContent=str;
    ,
    go:function(e)
        var s="p":this.id;
        chg.set(s.p);
        hstry.add(s);
    
;
var hstry=
    add:function(s)
        var u=new URL(window.location);
        u.searchParams.set("x",s.p);
        window.history.pushState(s,"",u);
    ,
    adjust:function(state)
        if(state.p)
            chg.set(state.p);
        
    
;
window.onpopstate=function(e)
    console.log("popstate, e.state:["+ JSON.stringify(e.state) +"]");
    hstry.adjust(e.state);

window.onload=function()
    var i,d,a=["p1","p2","p3","p4"];
    for(i=0;i<a.length;i++)
        d=document.getElementById(a[i]);
        d.addEventListener("click",chg.go,false);
    

</script>
</body>
</html>

【讨论】:

【参考方案19】:

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

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

【讨论】:

@MunamYousuf 很明显为什么这段代码不起作用。它跟踪浏览器的后退按钮,但该按钮位于视口之外,因此无法获得 clientX 和 clientY 的坐标。假设它有效,那么 ios 怎么样?后退按钮在底部......而且它的编写方式也非常低效,它是每个触发事件的条件......上帝太糟糕了,我会投反对票。 这太可怕了!你为什么要这样做? :/它甚至不会像詹姆斯所说的那样工作。 这很有趣的 AF,虽然 XD

以上是关于如何检测浏览器后退按钮事件 - 跨浏览器的主要内容,如果未能解决你的问题,请参考以下文章

如何使用角度检测浏览器后退按钮单击事件?

Javascript/jQuery 仅在浏览器后退/前进按钮单击时检测哈希更改

JavaScript 在粘贴事件中获取剪贴板数据(跨浏览器)

跨浏览器事件对象封装

检测 Iframe 内容何时加载(跨浏览器)

JavaScript在粘贴事件上获取剪贴板数据(跨浏览器)