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

367

如何确定用户是否在浏览器中按下了返回按钮?

如何使用#URL系统在单页面Web应用程序中强制使用内部返回按钮?

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


4
希望能够添加导航事件(后退/前进)。到目前为止,这是一个正在进行中的工作。https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming - Howdy
请支持对于 ongoback 事件的提案:https://discourse.wicg.io/t/set-back-button-url-in-pwas/4112 - collimarco
3
这些按钮是否不允许拦截,因为这样会使应用程序在页面上“锁定”浏览器,这种情况是否可能发生? - William T. Mallard
1
已经有300个赞,但还没有一个有效的答案。 - greendino
@greendino - 这个问题与API本身有关(没有解释为什么popstate不存储方向-后退与前进)。简直糟糕透顶。这里的所有答案都检测到“两个”事件(后退和前进)。还没有答案(九年后的hh)。 - undefined
22个回答

5

我能够使用该线程和其他资源中的一些答案使其在IE和Chrome/Edge上正常工作。对我来说,history.pushState 在IE11中不被支持。

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

3
我在Chrome 78.0.3904.70(正式版本)(64位)上尝试过,但并未成功。 - Jeffz

4
只有在重新定义 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
3
这似乎是一个聪明的历史记录机制。然而,代码没有注释,所以很难跟踪。如果有人导航离开当前页面,代码将无法检测到按钮按下,这并没有回答“如何确定用户是否按下了浏览器的后退按钮?”这个最初的问题。按钮按下和历史记录跟踪是两件不同的事情,尽管一个可能触发另一个。仍有讨论存在的事实表明Web浏览器设计上存在重大缺陷。 - CubicleSoft
CubicleSoft,让我们想象一下实际的转换(在单击链接时直接形成的页面)和虚拟的转换(在没有真正的单击通过时生成的页面)。在实际的转换中,(如果在转换过程中域名不同),由于安全策略和隐私问题,浏览器不允许您跟踪转换。使用动态转换,您的代码在您的操作上下文中执行,您有机会在导航面板中跟踪操作。至少对于您的SPA应用程序来说,这是令人满意的。 - AlexeyP0708

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

1
文档的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

这是我的一个侧边菜单实现,可以使用后退按钮关闭。 适用于Chrome/Edge/Android/...

<html>

<head>
<style>
    .sidebarMenu {
        height: 25%;
        position: fixed;
        left: 0;
        width: 25%;
        margin-top: 20px;
        transform: translateX(-100%);
        transition: transform 250ms ease-in-out;
        background-color: #aaa;
        opacity: 1;
    }

    .openSidebarMenu {
        transition: all 0.3s;
        box-sizing: border-box;
        display: none;
    }

    .openSidebarMenu:checked ~ #sidebarMenu {
        transform: translateX(0);
    }
</style>
</head>

<body>
<input type="checkbox" class="openSidebarMenu" id="openSidebarMenu" onclick="sidemenu()">
<label for="openSidebarMenu">
Menu (click me or back button to close)
</label>
<div id="sidebarMenu" class="sidebarMenu" onclick="sideBarClose()">
    <ul class="sidebarMenuInner">
        <li>Menu 1</li>
        <li>Menu 2</li>
    </ul>
</div>

<script>
    function sidemenu() {
        var selection = document.getElementById("openSidebarMenu");
        if (selection.checked) {
            window.history.pushState({page: 1}, "", "");
            checked = true;
            window.onpopstate = function(event) {
                var selection = document.getElementById("openSidebarMenu");
                if(event && selection.checked) {
                    selection.checked = false;
                    console.log("event 1");

                } else {
                    console.log("event 2");
                }
            }
        } else {
            history.back();
        }
    }
    
    function sideBarClose() {
        var selection = document.getElementById("openSidebarMenu");
        if (selection.checked) {
            selection.checked = false;
            history.back();
        }
    }   
</script>

</body>
</html>

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

0

如果您通过调用浏览器在应用程序中导航,浏览器将会发出 popstate 事件。

window.history.pushState({},'','/to')

如果您手动输入地址到地址栏并单击返回按钮,则不会触发popstate事件。

如果您使用此简化函数在应用程序中导航

const navigate = (to) => {
    window.history.pushState({}, ",", to);
  };

那么这将会起作用

const handlePopstate = () => {
  console.log("popped");
};
window.addEventListener("popstate", handlePopstate);

0

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
}

0

我正在寻找解决这个问题的方法,并根据这里的一些答案和MDN Web Doc页面上的History.pushState()WindowEventHandlers.onpopstate组合了一个简单的骨架测试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>

0
 <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
没有必要将状态存储在DOM中。您也不应该重新查询输入的DOM,而应将其存储为本地变量。如果您不想显示<input>,请使用<input type="hidden">。如果您想使用此方法,则更喜欢在“window”上使用变量,但即使如此也不明智。 - toastal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接