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

Posted

技术标签:

【中文标题】如何在单页移动 Web 应用程序中实现我自己的历史堆栈?【英文标题】:How to implement my own history stack in a single page mobile web application? 【发布时间】:2012-06-06 10:57:53 【问题描述】:

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

它可以与浏览器中的后退/前进按钮正常工作。

当用户导航到一个页面时, 内容从右侧滑入,而 内容从左侧滑出(并滑出视口)。如果用户按下“前进”浏览器按钮,我希望同样的事情发生。这一切都有效。

我有一个添加到 body 元素 navigate-back 的类,它将翻转此行为,因此当用户使用浏览器的后退按钮导航返回时,他们会看到内容滑动返回从左侧进入,其他内容滑入右侧。基本上与前进相反。

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

实现我自己的历史堆栈的最佳方法是什么,以便我可以检测用户是否在单页 Backbone 移动应用程序的上下文中向前/向后导航?

【问题讨论】:

澄清一下:您是否使用骨干路由在页面之间导航?您的页面是否有固有的顺序(从 B 页面到 A 页面总是“返回”)还是只是您浏览项目的顺序? @OlliM 我有一个主干路由器,但我不必为每次更改都调用router.navigate(),但是我确实有一个自定义事件在每个页面更改时触发。也没有明确的页面顺序。 好吧,我处理的问题是相关的,但不一样:我在 UI 中有按钮作为后退,并希望浏览器历史记录反映这一点。我对此有部分解决方案:我记录已添加到历史堆栈的页面,而不是 router.navigate 我检查页面是否已经在堆栈中,如果它已经在使用 history.go 而不是导航那里, router.navigate 如果没有。在正常情况下工作,但如果用户在应用程序中间重新加载页面,则状态丢失。 【参考方案1】:

我不了解backbone.js1,但我帮助开发了一个必须在html5 中完全实现这种行为的移动应用程序,所以我应该能够给出一些好的建议:

首先很高兴知道history.pushState 函数的存在。但它最大的问题是it is supported up to android 2.3, but not on android 3 till android 4.0.3。正如 kiranvj 正确指出的那样,这可以通过使用流行的 history.js 库来解决,该库为缺少历史记录功能提供了一个 polyfill 解决方案。

现在,解决您的实际问题,我实现历史方向动画的方式是将数据添加到pushState 函数(history.pushState(data,title,url)),我使用该函数确定了页面的逻辑位置。在我的应用程序中,我不仅限于水平条,而且在您的情况下,您将跟踪任何新加载页面的位置,该位置比当前页面高一个。例如

History.pushState(position:History.getState().data.position+1,"Your title","Your URL");

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

您还会注意到,我已经在首页上假设您拥有position:1,而通常首页没有状态信息。实现这一点的方法是使用history.replaceState,它将当前的空状态替换为更具信息性的状态。或者,您也可以检查前面提到的任何事件的空状态,如果它是空的,则认为它是最左边的 (position:1)。

希望这会有所帮助,如果您有任何其他问题,请随时提出。

请注意,此答案假定您使用的是history.js,如果您愿意,您需要侦听略有不同的事件(例如onpopstate)并使用略有不同的结构(history 而不是History)想要构建自己的解决方案。

还需要注意的是,可以使用您自己的队列数组来构建它,这会给您更多的控制权,但不能与浏览器的后退按钮结合使用。这是浏览器网站的一个大问题,但是如果您正在构建 cordova(又名phonegap)Web 应用程序,则要容易得多。


1 刚读过它,它似乎做了一些历史处理of its own,这可能使集成上述技术变得更加复杂。

【讨论】:

这听起来是个不错的解决方案。此外,history.js +1,尽管它的一些功能在主干.js 中可用;我听说主干的历史在 Safari 中有一些错误。 基于赏金评论“这个问题广泛适用于大量受众。需要详细的规范答案来解决所有问题。”我决定给出一个通用的答案,详细说明可以在任何情况下使用的技术,尽管我在脚注中确实提到过骨干自身的实现可能会造成麻烦。 @DavidMulder:+1 基于历史记录在页面刷新和/或导航到外部 url 并返回的假设。如果我错了,请纠正我,但这不是一个巨大的隐私漏洞吗?即任何有权访问历史记录的页面都可以跟踪用户在会话中访问过的先前站点。 @o.v.:您似乎误解了可以读出历史记录。唯一可以将“状态信息”与特定页面加载相关联的事情,当用户使用浏览器导航来回导航时,您可以再次读取该页面加载。当然,在您自己的页面中,您已经知道当前页面,这样就可以进行转换等操作。【参考方案2】:

如果您正在开发一个 true 单页应用程序,为什么不设置一个数组来将历史 url 保存在 js 变量中(而不是依赖于 history.pushState 和支持)?

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

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

更新:

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

【讨论】:

如何确定用户是返回、前进还是重新加载? @alex: 可以检查 url 是否与历史记录中的当前/上一个/下一个 url 匹配;然而,我现在的印象是,这可以通过更本地的骨干来完成。确认后会更新 这个问题是(正如我在回答中提到的)它在本地浏览器按钮必须工作的浏览器中是不可接受的,尽管如果在环境中工作,这是一个相当容易执行的解决方案像科尔达瓦。【参考方案3】:

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

最初我使用 html5 statechange 和 onhashchange。在一个或其他移动设备中都有一些问题。最后我从这里使用了 Zepto 历史插件https://github.com/browserstate/history.js

它在一定程度上解决了大部分问题。试试吧,它会很有用,它会尽可能处理 html4 和 html5 功能。

【讨论】:

History.js 是一个非常有用的库,但它如何帮助检测浏览器后退按钮的点击? 我在这个库中看不到任何东西来检测用户是否向后导航。你能详细说明吗? @kiranvj 你应该做个小提琴,zip 不是很方便。 我怀疑这是否适用于小提琴,因为我们在演示中使用浏览器后退和前进按钮。 @kiranvj:您的代码没有给出如何实现亚历克斯要求的方向行为的示例(您只显示了一个基本的历史示例),history.js 库也没有帮助直接地。 (刚刚添加了这条评论来解释我的反对意见)【参考方案4】:

在单页移动应用程序中使用这个东西,这将允许历史记录并将用户移回。

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

【讨论】:

【参考方案5】:

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

依靠“#/slide/123”可以支持硬页面重载,简化工作

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

【讨论】:

您描述的技术与我在回答中所做的相同,但有许多明显的缺点:哈希更改历史操作依赖于轮询,有很多浏览器怪癖,因此应该 如果存在pushState 则使用。应用程序无法在幻灯片中描述,因此使用您的方法,您会得到:#page/nameOfPage/numberDescribingPosition,接下来如果有人将 url 复制到哪里,numberDescribingPosition 将不再有意义。 @David 好点。除了哈希更改并不总是依赖轮询。有一个hashchange 事件。 如果页面没有顺序,那么左右滑动效果对我来说没有意义。尤其是后退效果。这是一个品味问题,但我会承诺以下两种情况之一:要么页面有顺序,因此用左右滑动效果突出显示它,或者它们没有顺序并选择其他一些不会让用户喜欢的效果正在滑动页面。即使没有顺序,您仍然可以将“最后一页的名称”保留在某个全局中,因此如果新的哈希=“上一页”的名称,则不是向前滑动,而是向后滑动。 @ddotsenko:顺序当然是用户访问页面的顺序。现在,整个问题是关于你在最后一句话中提出的我相信的事情,但是你提出的解决方案的问题是,一旦你四处走动它就会变得一团糟(回来两次, 转发两次)并且在这种情况下您需要保留自定义页面数组(最终得到类似于我的解决方案,除了状态数据没有链接到实际状态而是链接到数组中的位置(导致与重复名称)。 @alex:哇,我已经创建了各种应用程序,现在需要状态完整性,包括历史管理,并且不知道 hashchange 事件,我的错。嗯,支持 hashchange 而不是 popstate 的唯一两个重要的浏览器是(可悲的)IE8 和 9(30% 的市场份额),因此取决于您的受众,这可能非常有用。尽管如此,如果您需要旧版浏览器支持并且您确实需要轮询等。这对 *** 来说是相当痛苦的(“玩过”一次,但最终使用了一个库) .

以上是关于如何在单页移动 Web 应用程序中实现我自己的历史堆栈?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C 中实现我自己的基本 unix shell?

vue.js开发抓信插件,如何在单页应用中打开新窗口

在单页 Clojure Web 应用程序中使用 Friend 进行身份验证和授权

授权代码流如何在单页应用程序中工作?

在 Dockerfile 中实现我自己的 CMD 时是不是可以不覆盖基本映像 CMD?

使用地址栏在单页应用程序中路由用户