JS - window.history - 删除一个状态

Posted

技术标签:

【中文标题】JS - window.history - 删除一个状态【英文标题】:JS - window.history - Delete a state 【发布时间】:2015-03-17 16:12:03 【问题描述】:

使用 html5 window.history API,我可以在我的网络应用上很好地控制导航。

应用目前有两种状态:selectDate(1) 和enterDetails(2)。

当应用加载时,我replaceState 并设置了一个popState 监听器:

history.replaceState(stage:"selectDate",...,...);
window.onpopstate = function(event) 
    that.toStage(event.state.stage);
;

When a date is selected and the app moves to stage 2 I push state 2 onto the stack:

history.pushState(stage:"enterDetails",...,...);

此状态会在细节发生变化时被替换,因此它们会保存在历史记录中。

离开第二阶段的三种方式:

保存(ajax 提交) 取消 返回按钮

后退按钮由popstate 监听器处理。取消按钮推动第 1 阶段,以便用户可以返回到他们输入后退按钮的详细信息。这两个都很好用。

保存按钮应该返回到第 1 阶段并且不允许用户导航回详细信息页面(因为他们已经提交了)。基本上,它应该使历史堆栈长度 = 1。

但似乎没有history.delete()history.merge()。我能做的最好的事情是history.replaceState(stage1),它将历史堆栈保留为:["selectDate","selectDate"]

如何去掉一层?

编辑:

想到了别的办法,还是不行。

history.back(); //moves history to the correct position
location.href = "#foo"; // successfully removes ability to go 'forward', 
                       // but also adds another layer to the history stack

这会将历史堆栈保留为["selectDate","selectDate#foo"]

那么,作为替代方案,有没有办法在不推动新状态的情况下删除“前进”历史记录?

【问题讨论】:

【参考方案1】:

你现在可能已经继续前进了,但是……据我所知,没有办法删除历史条目(或状态)。

我一直在研究的一个选项是在 javascript 中自己处理历史记录,并使用 window.history 对象作为各种载体。

基本上,当页面首次加载时,您会创建自定义历史记录对象(我们将在此处使用数组,但使用对您的情况有意义的任何内容),然后执行您的初始pushState。我会将您的自定义历史记录对象作为状态对象传递,因为如果您还需要处理用户离开您的应用并稍后返回的情况,它可能会派上用场。

var myHistory = [];

function pageLoad() 
    window.history.pushState(myHistory, "<name>", "<url>");

    //Load page data.

现在,当您导航时,您可以添加到您自己的历史记录对象(或者不添加 - 历史记录现在掌握在您手中!)并使用 replaceState 使浏览器远离循环。

function nav_to_details() 
    myHistory.push("page_im_on_now");
    window.history.replaceState(myHistory, "<name>", "<url>");

    //Load page data.

当用户向后导航时,他们将达到您的“基本”状态(您的状态对象将为空),您可以根据自定义历史对象处理导航。之后,您执行另一个 pushState。

function on_popState() 
    // Note that some browsers fire popState on initial load,
    // so you should check your state object and handle things accordingly.
    // (I did not do that in these examples!)

    if (myHistory.length > 0) 
        var pg = myHistory.pop();
        window.history.pushState(myHistory, "<name>", "<url>");

        //Load page data for "pg".
     else 
        //No "history" - let them exit or keep them in the app.
    

用户将永远无法使用浏览器按钮向前导航,因为它们始终位于最新页面上。

从浏览器的角度来看,每次“返回”时,它们都立即再次向前推进。

从用户的角度来看,他们能够在页面中向后导航,但不能向前导航(基本上模拟智能手机“页面堆栈”模型)。

从开发人员的角度来看,您现在可以高度控制用户如何浏览您的应用程序,同时仍然允许他们使用浏览器上熟悉的导航按钮。您可以随意从历史链中的任何位置添加/删除项目。如果您在历史记录数组中使用对象,您还可以跟踪有关页面的额外信息(如字段内容等)。

如果您需要处理用户发起的导航(例如用户在基于哈希的导航方案中更改 URL),那么您可以使用稍微不同的方法,例如...

var myHistory = [];

function pageLoad() 
    // When the user first hits your page...
    // Check the state to see what's going on.

    if (window.history.state === null) 
        // If the state is null, this is a NEW navigation,
        //    the user has navigated to your page directly (not using back/forward).

        // First we establish a "back" page to catch backward navigation.
        window.history.replaceState(
             isBackPage: true ,
            "<back>",
            "<back>"
        );

        // Then push an "app" page on top of that - this is where the user will sit.
        // (As browsers vary, it might be safer to put this in a short setTimeout).
        window.history.pushState(
             isBackPage: false ,
            "<name>",
            "<url>"
        );

        // We also need to start our history tracking.
        myHistory.push("<whatever>");

        return;
    

    // If the state is NOT null, then the user is returning to our app via history navigation.

    // (Load up the page based on the last entry of myHistory here)

    if (window.history.state.isBackPage) 
        // If the user came into our app via the back page,
        //     you can either push them forward one more step or just use pushState as above.

        window.history.go(1);
        // or window.history.pushState( isBackPage: false , "<name>", "<url>");
    

    setTimeout(function() 
        // Add our popstate event listener - doing it here should remove
        //     the issue of dealing with the browser firing it on initial page load.
        window.addEventListener("popstate", on_popstate);
    , 100);


function on_popstate(e) 
    if (e.state === null) 
        // If there's no state at all, then the user must have navigated to a new hash.

        // <Look at what they've done, maybe by reading the hash from the URL>
        // <Change/load the new page and push it onto the myHistory stack>
        // <Alternatively, ignore their navigation attempt by NOT loading anything new or adding to myHistory>

        // Undo what they've done (as far as navigation) by kicking them backwards to the "app" page
        window.history.go(-1);

        // Optionally, you can throw another replaceState in here, e.g. if you want to change the visible URL.
        // This would also prevent them from using the "forward" button to return to the new hash.
        window.history.replaceState(
             isBackPage: false ,
            "<new name>",
            "<new url>"
        );
     else 
        if (e.state.isBackPage) 
            // If there is state and it's the 'back' page...

            if (myHistory.length > 0) 
                // Pull/load the page from our custom history...
                var pg = myHistory.pop();
                // <load/render/whatever>

                // And push them to our "app" page again
                window.history.pushState(
                     isBackPage: false ,
                    "<name>",
                    "<url>"
                );
             else 
                // No more history - let them exit or keep them in the app.
            
        

        // Implied 'else' here - if there is state and it's NOT the 'back' page
        //     then we can ignore it since we're already on the page we want.
        //     (This is the case when we push the user back with window.history.go(-1) above)
    

【讨论】:

嘿,谢谢。这比我一直使用的解决方法更优雅。 当用户习惯了这种行为时该怎么办,然后导航几次建立一个状态,然后导航到外部站点,然后导航回来,我假设所有应用程序导航的历史是现在不见了,后退按钮将在堆上的最后一个。 history.pushState 推送的状态将在他们回来时可用(如果他们关闭或刷新浏览器)。一些浏览器会在页面加载时触发 popstate 事件,但其他浏览器不会(例如 Firefox)。但是你总是可以直接阅读 history.state 。如果您不使用 history.state 对象,则需要使用另一种方式来持久化您的状态(如 localStorage)。 history.state 会作为浏览历史记录的一部分自动保存。 如您所说myHistory.push("page_im_in_now"),如何将当前页面推送到myHistory?请告诉我一个例子 不错的解决方案!一个小问题可能是一次无法返回多个步骤。浏览器的历史将始终只显示此类应用程序的两个条目。【参考方案2】:

没有办法删除或读取过去的历史记录。

您可以尝试通过在自己的内存中模拟历史记录并在每次发出窗口popstate 事件时调用history.pushState 来绕过它(由the currently accepted Mike's answer 提出),但它有很多缺点会导致比在您的动态 Web 应用程序中根本不支持浏览器历史记录更糟糕的用户体验,因为:

popstate 事件可能在用户回到过去的 ~2-3 个状态时发生 用户前进时会发生 popstate 事件

因此,即使您尝试通过构建虚拟历史来绕过它,它也很可能会导致您有空白历史状态(返回/前进没有任何作用),或者返回/ forward 完全跳过你的一些历史状态。

【讨论】:

尝试进行后退/前进工作时的另一个问题是,当状态被推送时,标题会保存到历史记录中。现在,当用户使用查看历史记录或右键单击后退按钮时,如果您尝试做任何聪明的事情,用户可能会被带到他们没想到的页面。 其中一些问题很容易解决。对推送状态进行索引是一个好的开始——类似于stateDepth 属性,在装饰的pushState 中递增,旁边是prevState。如果prevState.stateDepth === 4,而您现在位于state.stateDepth === 2,那么您很清楚自己回来了多远

以上是关于JS - window.history - 删除一个状态的主要内容,如果未能解决你的问题,请参考以下文章

js基础之Window History(BOM)

window.history 方法总结

JavaScript-Runoob-JS 浏览器BOM:JavaScript Window History

js屏蔽手机的物理返回键

Google DFP Refresh Ads 删除了 Internet Explorer 10 中的 window.history 堆栈

js funciton总结