如果 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 状态属性为空时我该怎么办?