如果 popstate 事件来自带有 HTML5 推送状态的后退或前进操作,我如何检索?

Posted

技术标签:

【中文标题】如果 popstate 事件来自带有 HTML5 推送状态的后退或前进操作,我如何检索?【英文标题】:How do I retrieve if the popstate event comes from back or forward actions with the HTML5 pushstate? 【发布时间】:2012-02-17 07:28:28 【问题描述】:

我正在开发一个网页,根据我执行相应动画的下一个或后退操作,使用 pushstate 时会出现问题。当我收到事件时,我如何知道用户是否使用 Pushstate API 单击了后退或前进历史按钮?还是我必须自己实现某些东西?

【问题讨论】:

popstate 事件显示“请更改为此状态”。它看起来好像假设您知道您当前处于什么状态,因此您需要做什么来更改状态。 问题是历史是一个堆栈,所以如果我有一个列表,我前进,前进,前进,后退,前进,后退,前进,列表是:[1,2 ,3,4,5],历史将是:[1,2,3,4,3,4,3,4]。数字很​​容易,但使用 url 并不那么容易知道哪个 url 是下一个,哪个是前一个。 bennedich 下面的评论对我帮助很大。接受它可能会很好,这样它也能更有效地帮助他人。 【参考方案1】:

你必须自己实现它,这很容易。

在调用 pushState 时,为数据对象提供一个唯一递增的 id (uid)。 当onpopstate处理程序被调用时;根据包含最后一个状态 uid 的持久变量检查状态 uid。 使用当前状态 uid 更新持久变量。 根据状态 uid 是大于还是小于上一个状态 uid 执行不同的操作。

【讨论】:

当页面/视图层次结构明确定义时,这很有效,并且某个页面接连出现总是正确的。当导航可以更加动态时,您不能依赖递增的 UID。我已经通过使用 sessionStorage 来维护最后 X 页的堆栈来解决这个问题。然后,在 popstate 事件上,您可以从 sessionStorage 检查堆栈,以查看新页面 URL 是否与 N-2 页面的 URL 相同。使用 sessionStorage 而不是普通变量,因此它在页面边界之间保持不变。 @frontendbeauty 我认为您应该使用代码 sn-p 发布您的建议作为答案。 此外,您可以编写一个状态更改管理器来检查 id 以查看当前状态是什么以及它正在移动到什么状态,而不是检查值是否更大/更小,然后执行对应的动画 如果这么简单,你为什么不写实现呢?对于任何可能的用例,我根本看不到如何使其无错误。无论我将当前状态保留在哪里,我都可以弄清楚如何滥用它。 @frontendbeauty 如果你在页面上你在中间页面 b 并且你的堆栈看起来像这样[a, b, a] 你得到popstate 事件,你怎么知道它是后退还是前进? 【参考方案2】:

这个答案应该适用于单页推送状态应用程序, 或多页应用程序,或两者的组合。 (更正以修复 Mesqualito 评论中提到的 History.length 错误。)

工作原理

我们可以轻松地监听历史堆栈的新条目。 我们知道,对于每个新条目,specification 要求浏览器:

    “删除当前条目之后浏览上下文会话历史记录中的所有条目” “在末尾添加一个新条目”

在进入的那一刻,因此:

新条目位置 = 最后显示的位置 + 1

那么解决办法是:

    用自己在堆栈中的位置标记每个历史记录条目 在会话存储中跟踪最后显示的位置 通过比较两者来发现行进方向

示例代码

function reorient() // After travelling in the history stack

    const positionLastShown = Number( // If none, then zero
      sessionStorage.getItem( 'positionLastShown' ));
    let position = history.state; // Absolute position in stack
    if( position === null ) // Meaning a new entry on the stack
    
        position = positionLastShown + 1; // Top of stack

        // (1) Stamp the entry with its own position in the stack
        history.replaceState( position, /*no title*/'' );
    

    // (2) Keep track of the last position shown
    sessionStorage.setItem( 'positionLastShown', String(position) );

    // (3) Discover the direction of travel by comparing the two
    const direction = Math.sign( position - positionLastShown );
    console.log( 'Travel direction is ' + direction );
      // One of backward (-1), reload (0) and forward (1)


addEventListener( 'pageshow', reorient );
addEventListener( 'popstate', reorient ); // Travel in same page

另请参阅代码的live copy。

限制

此解决方案忽略外部页面的历史条目, 对应用程序来说是陌生的,就好像用户从未访问过它们一样。 它仅根据最后显示的应用程序页面计算行进方向, 无论其间访问的任何外部页面如何。 如果您希望用户将外部条目推送到堆栈中 (请参阅 Atomosk 的评论),那么您可能需要一种解决方法。

【讨论】:

在您的网站上做一些导航,打开一个外部链接,按浏览器后退按钮两次,长按浏览器前进按钮并选择外部站点会话存储,按返回。也许我太谨慎了:)。另外我认为您的功能可以替换为const direction = Math.sign(history.length - (sessionStorage.getItem('stateLastShown') || 0)); sessionStorage.setItem('stateLastShown', history.length);,因为您总是将history.length 置于历史状态。 @Atomosk (1) 没错,外部链接处理不当。我添加了一个警告。 (2)我认为你的修剪代码会失败。 History.length 仅确定堆栈中新条目 的位置。您还需要代码来处理另一种情况,即 revisited 条目。 是的,你说得对。我永远不会习惯前端 api 的糟糕程度。检查我可以使用后退/前进按钮走多远应该是一行,但有人另有决定。 浏览器后退/前进历史有一个固定限制,在 Chrome 中为 50。当您达到该限制时,history.length 将始终产生相同的数字(在我的情况下为 50),从而使此解决方案始终返回0 作为方向,因为所有状态条目的位置都相同。 @Mesqalito 你说得对,History.length 的浏览器限制使其不可靠。我将答案改为使用会话变量positionLastShown

以上是关于如果 popstate 事件来自带有 HTML5 推送状态的后退或前进操作,我如何检索?的主要内容,如果未能解决你的问题,请参考以下文章

HTML5历史状态管理history API-pushState/replaceState与popstate事件

HTML5历史状态管理history API-pushState/replaceState与popstate事件

带有 history.pushState 和 popstate 的 Ajax - 当 popstate 状态属性为空时我该怎么办?

jquery mobile+html5 写的页面 如何禁用手机后退按钮。或者说如何禁用页面后退。

jQuery绑定popstate事件未通过

利用popstate事件和window下的history对象处理浏览器跳转问题