如何在单页面移动Web应用程序中实现自己的历史记录堆栈?

22

我有一个使用Backbone和Zepto开发的单页面移动应用程序。

在浏览器中,回退/前进按钮可以正常工作。

当用户导航到一个页面时,内容从右侧滑入,内容向左滑出(并离开视口)。如果用户按"前进"浏览器按钮,我想要相同的效果。这一切都有效。

我有一个类,我会将其添加到body元素中: navigate-back,以翻转此行为。因此,当用户使用浏览器的后退按钮导航回去时,他们会看到内容从左边滑动回来,而其他内容则从右边滑入。基本上只是前面相反的效果。

我需要检测用户是否正在向后导航,以便调用备用行为。我尝试了实现自己的历史记录栈,但遇到了很多问题,有时候会将前进标记为后退导航,这会破坏视觉提示。它已经降为一堆hack,如果我发布它,可能只会让我感到尴尬。

在单页Backbone移动应用程序的上下文中,最佳方法是如何实现自己的历史记录栈,以便检测用户是否正向/反向导航?


请澄清一下:您是否使用backbone路由来在页面之间导航?您的页面是否具有固有顺序(从页面B到页面A始终是“返回”),还是只是您浏览项目的顺序? - OlliM
@OlliM 我有一个 Backbone 路由器,但我不一定为每个更改调用 router.navigate(),但是当每个页面更改时,我有一个自定义事件被触发。也没有定义页面的顺序。 - alex
好的,我处理的问题与此相关但不完全相同:我在UI中有作为返回按钮的按钮,并希望浏览器历史记录反映出这一点。我找到了一个部分解决方案:我记录了已添加到历史堆栈中的页面,而不是使用router.navigate,我检查页面是否已经在堆栈中,如果已经存在,则使用history.go而不是navigate,如果不存在则使用router.navigate。在正常情况下可以工作,但如果用户在应用程序中间重新加载页面,则状态会丢失。 - OlliM
5个回答

23
我不了解backbone.js1,但我曾帮助开发过一个移动应用程序,需要在html5中实现这个行为,所以我应该能够给出一些好的建议:
首先要知道history.pushState函数的存在。但它的问题是它支持Android 2.3,但不支持Android 3到Android 4.0.3。正如kiranvj正确指出的那样,可以使用流行的history.js库来提供缺少历史功能的polyfill解决方案。
现在,针对您实际的问题,我实现历史方向动画的方法是通过向pushState函数添加数据(history.pushState(data,title,url))来确定页面的逻辑位置。在我的应用程序中,我不仅限于水平栏,但在您的情况下,您将跟踪任何新加载的页面的位置,其位置比当前页面高1。例如:
History.pushState({position:History.getState().data.position+1},"Your title","Your URL");

接下来,当window.onstatechangewindow.onanchorchange事件触发时,您需要观察位置是否比当前页面高或低(例如,通过使用history.js中我上面使用的History.getState()函数),并根据此决定向哪个方向移动(较低的向左移动,较高的向右移动),如下图所示:

Illustration of history events

您还会注意到,我在第一页上已经假设您的位置是{position:1},而通常第一页不会有状态信息。实现这一点的方法是使用history.replaceState将当前空状态替换为更详细的状态。或者,您也可以在任何先前提到的事件中检查空状态,如果为空,则认为它是最左边的一个({position:1})。
希望这能帮到您,如果您有任何其他问题,请随时问。
请注意,此答案假定您正在使用history.js,如果您想构建自己的解决方案,则需要监听略有不同的事件(例如onpopstate)并使用略有不同的结构(history而不是History)。
值得注意的是,你也可以使用自己的队列数组来构建它,这样可以更加灵活地控制,但与浏览器的后退按钮不兼容。对于浏览器站点来说,这是一个很大的问题,但如果你正在构建一个cordova(又名phonegap) Web应用程序,则会更加容易。

1 只是阅读了一些相关资料,似乎该技术会有一些自己的历史处理方式, 这可能会使其整合上述描述的技术变得更加复杂。


这听起来是一个不错的解决方案。此外,赞一个 history.js,尽管它的一些功能在 backbone.js 中也可用;我听说 backbone 在 Safari 中的历史记录有一些 bug。 - meetamit
根据带有悬赏的评论“这个问题适用于广大受众。需要详细的规范答案来解决所有问题。”,我决定提供一个通用的答案,详细介绍了可以在任何情况下使用的技术,尽管我在脚注中也提到了backbone自己的实现可能会引起麻烦。 - David Mulder
@DavidMulder:+1 基于历史记录在页面刷新和/或导航到外部URL并返回时保留的假设。如果我错了,请纠正我,但这是否会成为一个巨大的隐私漏洞呢?即任何可以访问历史记录的页面都可以跟踪用户在会话期间访问过的上一个站点。 - Oleg
@o.v.:您似乎误解了可以读取历史记录的可能性。唯一可能的是将“状态信息”与某个页面加载相关联,当用户使用浏览器导航前进和后退时,您可以再次读取该信息。当然,在您自己的页面中,您已经知道当前页面,因此这使得转换等操作成为可能。 - David Mulder

1

在移动设备上使用Zepto进行单页面-多视图工作时,我遇到了同样的问题。

最初我使用了html5 statechange和onhashchange。但是在某些移动设备上都存在一些问题。最终我使用了来自https://github.com/browserstate/history.js的Zepto历史插件。

它在很大程度上解决了大部分问题。尝试一下吧,它会很有用的,它可以在可能的情况下处理html4和html5功能。


1
History.js是一个非常有用的库,但它如何帮助检测浏览器的后退按钮点击呢? - theotheo
我在这个库中没有看到任何可以检测用户是否正在向后导航的内容。您能否详细说明一下? - alex
@kiranvj 你应该创建一个fiddle,zip不是很方便。 - alex
我对这个在fiddle上是否能正常工作存在疑虑,因为我们在演示中使用了浏览器的后退和前进按钮。 - kiranvj
2
@kiranvj:你的代码没有展示如何实现Alex所要求的方向行为(你只展示了一个基本的历史记录示例),而且history.js库也不能直接帮助解决这个问题。(我只是加了这个评论来解释我的投票理由) - David Mulder

1

如果您正在开发一个真正的单页应用程序,为什么不设置一个数组来保存历史URL,并将其存储在JS变量中(而不是依赖于像history.pushState及其支持这样的东西)?

每当导航到新页面时,您可以将其URL推入数组中,每当按下“返回”按钮时,您可以检索所需的URL,直到您想要的位置。只要在用户返回几步然后导航到新链接时正确丢弃URL,这将完美地工作。

我从未尝试过将其实现为页面历史记录,但对于页面内撤消重做逻辑,这非常有效。

更新:

经过进一步研究,上述方法对于页面重新加载将无法使用,因为它是在JS可用的历史处理之外发生的操作。它仍将用于跟踪向后/向前转换,但此类历史记录将在导航到应用程序外部的URL或页面刷新时丢失。 David Mulder的答案似乎缺乏这种限制,因为它依赖于浏览器级别的历史记录,该历史记录在页面范围之外持久存在。


1
如何确定用户是在返回、前进还是重新加载页面呢? - alex
@alex:可以检查URL是否与历史记录中的当前/上一个/下一个URL匹配;但我现在的印象是,这可以通过backbone更本地地完成。确认后会更新。 - Oleg
这个问题(正如我在我的答案中提到的)的问题在于,它在浏览器内是不可接受的,因为本机浏览器按钮必须工作,尽管如果在像 cordava 这样的环境中工作,这是一个相当容易的解决方案。 - David Mulder

-1

在单页移动应用程序中使用此功能,可以允许用户查看历史记录并返回。

function onBackKeyDown() {
    history.go(-1);
    navigator.app.backHistory();
}

-2

Sammy.js的v.6.x分支(仅依赖哈希更改的分支)是跟踪历史记录的最完美、最简单、最兼容浏览器的方法。在那里,根本不会跟踪历史记录,因为Sammy只是观察哈希更改。

依赖于“#/slide/123”可以支持硬页面重新加载,并简化工作。

在每个页面视图中剥离最后一部分(幻灯片编号),并将其推入全局。在新路由上,查看数字是否大于或小于存储在全局中的数字,并执行正确的(左或右)动画。如果全局未定义,则没有动画。


1
你正在描述与我在答案中提到的相同技术,但有许多明显的缺点:哈希更改历史记录依赖于轮询,存在许多浏览器怪异行为,因此如果存在pushState,就不应该使用。一个应用程序无法仅通过幻灯片来描述,因此使用你的方法,你将得到:#page/nameOfPage/numberDescribingPosition,接下来,如果有人复制URL,则numberDescribingPosition将不再有意义。 - David Mulder
@David 的观点很好。除了哈希更改并不总是依赖轮询。还有一个 hashchange 事件。 - alex
如果页面没有顺序,那么左右滑动效果对我来说就没有意义。特别是滑动返回效果。这是一个品味问题,但我会选择其中之一:要么页面有顺序,并用向左、向右滑动的效果突出它,要么它们没有顺序,选择一些其他不让用户感觉他们正在浏览页面的效果。即使没有顺序,您仍然可以在某个全局变量中保留“上一页的名称”,因此如果新哈希=“上一页”的名称,而不是向前滑动,您将向后滑动。 - ddotsenko
@ddotsenko:当然,顺序是用户访问页面的顺序。现在,整个问题都是关于您在最后一句中提出的事情,但是您提出的解决方案的问题在于,一旦您进行移动(如向后两次,向前两次),它就会混乱,并且您需要保留自定义页面数组(最终得到类似于我的解决方案,但状态数据不是与实际状态相链接而是与数组位置相链接,导致重复名称问题)。 - David Mulder
@alex:哇,我现在已经创建了各种需要状态管理的应用程序,包括历史记录管理,但是我不知道hashchange事件,我的错。唔,只有两个支持hashchange而不支持popstate的浏览器(可悲的是)IE8和9(占30%的市场份额),所以根据你的受众群体,这可能非常有用。不过,如果您需要支持旧版浏览器并且确实需要轮询等功能,那就很麻烦了(曾经尝试过,但最终使用了库)。 - David Mulder
Backbone.history + Backbone routing 已经使用了这种技术。没有理由自己重新造轮子。 - 1nfiniti

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