如果 popstate 事件来自 HTML5 pushstate 的后退或前进操作,我如何检索?

IT技术 javascript html browser-history pushstate
2021-03-12 04:17:26

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

2个回答

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

  • 调用时pushState给数据对象一个唯一的递增 id (uid)。
  • onpopstate处理程序被调用时;根据包含最后一个状态 uid 的持久变量检查状态 uid。
  • 使用当前状态 uid 更新持久变量。
  • 根据状态 uid 是大于还是小于上一个状态 uid 执行不同的操作。
当页面/视图层次结构明确定义时,这很有效,并且某个页面接一个接一个地出现总是正确的。当导航可以更加动态时,您不能依赖递增的 UID。我通过使用 sessionStorage 来维护最后 X 页的堆栈解决了这个问题。然后,在 popstate 事件中,您可以从 sessionStorage 检查堆栈,以查看新页面 URL 是否与 N-2 页面的 URL 相同。使用 sessionStorage 而不是普通变量,因此它会在页面边界之间保持不变。
2021-04-24 04:17:26
@frontendbeauty 我认为您应该将您的提案作为代码片段的答案发布。
2021-04-27 04:17:26
另外,与其检查值是否更大/更小,您可以编写一个状态更改管理器来检查 id 以查看当前状态是什么以及它正在移动到什么状态,然后做相应的动画
2021-05-01 04:17:26
既然这么简单,为什么不写实现呢?我只是看不出如何使它在任何可能的用例中都没有错误。无论我在哪里保持当前状态,我都可以弄清楚如何滥用它。
2021-05-08 04:17:26
@frontendbeauty 如果您在页面 b 中,并且您的堆栈看起来像这样,[a, b, a]您会收到popstate事件,您怎么知道它是向后还是向前?
2021-05-12 04:17:26

此答案应适用于单页推送状态应用程序、多页应用程序或两者的组合。 (更正以修复History.lengthMesqualito 评论中解决的错误。)

怎么运行的

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

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

因此,在进入时:

新入场位置 = 最后显示的位置 + 1

那么解决办法是:

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

示例代码

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

另请参阅代码实时副本

局限性

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

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