我如何删除窗口历史状态?

67

使用HTML5 window.history API,我可以在我的Web应用程序上很好地控制导航。

该应用程序当前有两个状态:selectDate (1) 和 enterDetails (2)。

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

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

当选择日期后,应用程序进入第二阶段时,我将状态2压入堆栈:

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

每当详细信息发生更改时,此状态都会被替换,以便将其保存在历史记录中。

有三种方法可以离开阶段2:

  • 保存(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"]

那么,有没有一种方法可以在不推入新状态的情况下删除“向前”历史记录的方式作为替代方案?

3个回答

82

也许你已经放弃了,但是据我所知,没有办法删除历史记录条目(或状态)。

我正在探索的一种选择是在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.
}

当用户向后导航时,他们将进入您的“基本”状态(您的状态对象将为null),您可以根据自定义历史记录对象处理导航。之后,您再进行一次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)
    }
}

嘿,谢谢。这比我一直在使用的解决方法更优雅。 - DanielST
当用户返回时(即使关闭或刷新浏览器),由history.pushState推送的状态将可用。一些浏览器会在页面加载时触发popstate事件,但其他浏览器则不会(例如Firefox)。但是您始终可以直接读取history.state。如果您没有使用history.state对象,则需要使用另一种方式来保持状态(例如localStorage)。history.state作为浏览历史记录的一部分自动持久化。 - Mike B.
如何将当前页面推入myHistory,就像您所说的 myHistory.push("page_im_in_now")?请给我一个例子。 - Mr_Perfect
不错的解决方案!一个小问题可能是无法一次性回退多个步骤。对于这样的应用程序,浏览器的历史记录将始终显示两个条目。 - Andrej
1
@Andrej - 绝对正确,浏览器无法返回多个步骤!我还没有想出任何好的解决方案。我的意思是,你可能可以为每个导航推送一个新的“后退”页面,并找出用户返回了多远,如果他们点击了其中一个,但当你修改自定义历史堆栈时,这可能会变得混乱。可能有更好的选择,但我还没有在自己的使用中需要它,所以我没有深入研究这个特定的问题。 :) - Mike B.
显示剩余13条评论

5

无法删除或读取过去的历史记录。

你可以尝试通过在自己的内存中模拟历史记录并在每次窗口popstate事件被触发时调用history.pushState(这是当前接受的Mike的答案提出的),但这有很多缺点,将导致甚至比不支持浏览器历史记录更糟糕的用户体验,因为:

  • 当用户返回到过去的大约2-3个状态时,popstate事件可能会发生
  • 当用户向前移动时,popstate事件可能会发生

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


1
尝试使后退/前进功能正常工作时的另一个问题是,当状态被推送时,标题会保存到历史记录中。现在,当用户使用“查看历史记录”或右键单击后退按钮时,如果您尝试执行任何智能操作,用户可能会被带到他们没有预期的页面。 - robocat
其中一些问题解决起来相当简单。索引推送状态是一个不错的开始 - 例如一个stateDepth属性,它在装饰的pushState中递增,以及一个prevState。如果prevState.stateDepth === 4,而现在你在state.stateDepth === 2,那么你就大概知道你需要回退多少步了。 - Nick Bull

0
一个简单的解决方案:
var ismobilescreen = $(window).width() < 480;
var backhistory_pushed = false;

$('.editbtn').click( function()
{
    // push to browser history, so back button will close the editor 
    // and not navigate away from site
    if (ismobilescreen && !backhistory_pushed)
    {
        window.history.pushState('forward', null, window.location);
        backhistory_pushed = true;
    }
}

然后:

if (window.history && window.history.pushState) 
{
    $(window).on('popstate', function() 
    {
        if (ismobilescreen && backhistory_pushed && $('.editor').is(':visible'))
        {
            // hide editor window (we initiate a click on the cancel button)
            $('.editor:visible .cancelbtn').click();
            backhistory_pushed = false;
        }
    });
}

结果为:

  1. 用户打开编辑器 DIV,历史状态被保存。
  2. 用户点击返回按钮,历史状态被考虑在内。
  3. 用户留在页面上!
  4. 不是导航回去,而是关闭编辑器 DIV。

一个问题:如果您在 DIV 上使用“取消”按钮并隐藏编辑器,则用户必须点击移动设备的返回按钮两次才能返回到先前的 URL。

为了解决这个问题,您可以调用 window.history.back(); 自己删除历史记录条目,实际上删除所请求的状态

例如:

$('.btn-cancel').click( function() 
{
    if (ismobilescreen && backhistory_pushed)
    {
        window.history.back();              
    }
}

或者你可以将一个包含锚点的URL推入历史记录中,例如#editor,然后根据最近的URL是否存在该锚点来决定是否推入历史记录。


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