[极致用户体验] 多页面应用里,「网页内返回」按钮,何时用 history.back 何时用 replaceState?

本文探讨了在多页面应用中如何实现极致用户体验的网页内返回功能。作者HullQin指出,应当根据历史记录栈控制浏览器的前进和返回行为。在某些情况下,调用`history.back()`或`history.replaceState()`是关键。在单页面应用中,通过携带标识可以轻松实现返回,而在多页面应用中,需要解决跨页面传递标识和页面刷新的问题。文章提供了详细的解决方案和代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。

背景

上篇文章《网页里的「返回」应该用 history.back 还是 push ?》论证了单页面应用(Single-Page Application,简称SPA)如何实现网页内的「返回」按钮,本篇文章将会论证多页面应用(Multi-Page Application,简称MPA)如何实现网页内的「返回」按钮。

何谓「极致用户体验」

上文我提到,网站应该是有页面层级的:

1.png

它是一个树状结构,每个页面、模块划分非常清晰。

如果要追求极致用户体验,用户在浏览器点击「前进」或「返回」时,应该遵循这样的规则:

  • 点浏览器的「前进」按钮(forward,右箭头),只允许相邻页面层级从左往右跳转
  • 点浏览器的「返回」按钮(back,左箭头),只允许相邻页面层级从右往左返回

实现方案

要实现这样的规则,开发者必须控制好浏览器的历史记录栈:

  • 用户进入更深的页面层级,浏览器的历史记录栈就增1。
  • 用户返回更浅的页面层级,浏览器的历史记录栈就减1。但历史记录栈无法减1时,可以让历史记录栈数量保持不变。

我解释一下,开发者怎么控制历史记录栈?

什么时候历史记录栈增一? 当我们调用history.pushState()时,浏览器历史记录栈就会新增一个历史记录,主要存了URL等信息。此时,用户点击「浏览器返回」和「浏览器前进」,就可以在「上一个页面」和「当前页面」反复横跳。

什么时候历史记录栈减一? 当我们调用history.back()时,就可以让浏览器历史记录栈减一。其实严格来说不算减一,只是页面回退到了上一条记录,这相当于用户点了「浏览器返回」按钮。

有时候点「网页返回」按钮,不能直接调用history.back,为什么? 如果调用history.back()会返回其它界面(或者用户是直接打开了我们的某个页面,没有上一条历史记录了,「浏览器返回」按钮也是灰色),即调用history.back()无法返回我们自己网站的上一页面层级,就应该调用history.replaceState(),跳到上一页面层级。注意不能用history.push,如果用了push会打破我们的原则,那时候再点「浏览器返回」就从左往右导航了,违背了我们的网站页面层级。

回顾单页面应用方案

只要父页面跳转到子页面时,携带个「标识」,告知子页面,跳转来源是你亲爸爸。子页面就知道了,自页面的「网页返回」按钮,可以直接触发history.back()返回。

如果子页面发现没有「标识」,说明不是亲爸爸跳转到该子页面的,通过history.back()无法返回亲爸爸页面。不得不通过history.replaceState()前往亲爸爸页面,并且去的时候,不能带「标识」,因为子页面不是父页面的亲爸爸。

跳转时的「标识」,可以用history.pushState()中的state来实现。绝不能用URL中的参数来实现。因为URL太容易伪造了,可能用户点个收藏、复制个网址,就把标识给带上了。但是state绝对足够隐蔽。

多页面应用方案

问题描述

我的父页面 game.hullqin.cn 和 子页面 game.hullqin.cn/wzq 是部署了两套前端代码,他们是MPA。

在子页面有个「游戏列表」按钮,相当于我的「网页返回」按钮。我期望这两个页面符合网站页面层级标准:

  • 如果可以通过hittory.back()返回首页就用它。
  • 如果hittory.back()无法返回首页,就用history.replaceState()

2.png

难点1

直接调用history.pushState(),可以传递state标识,但这只会修改URL,并不会触发浏览器刷新,网页依然停留在父页面。

直接调用window.location.href = 'game.hullqin.cn/wzq'会使浏览器刷新,但是不能传递state标识。可能还需要借助sessionStorage方案来保存、传递「标识」,但这又引入了更高的复杂度,因为它是跟历史记录栈无关的,我们不得不在sessionStorage中存一些路由信息,才能正确传递「标识」。

解决难点1

先调用history.pushState(),传递state标识,再调用window.location.reload()触发刷新。这会保持state给下一个页面。

难点2

如果我们是通过调用history.pushState()来增加浏览器历史记录栈的,那么我们调用history.back()时,页面不会刷新,只改变URL。

也许你就说:像刚才一样,调用history.back()后再调用window.location.reload()触发刷新,不就解决了吗?

但这里还有一点:用户点击「浏览器返回」按钮时,只会改URL,页面不会刷新。虽然网址已经是首页了,但是界面依然是在game.hullqin.cn/wzq这个子页面。

类似的,用户在父页面点「浏览器前进」按钮准备进入game.hullqin.cn/wzq这个子页面时,也会只改URL,页面不刷新。

解决难点2

监听window.onpopstate事件,这个事件会在用户点「浏览器返回」按钮或「浏览器前进」按钮时触发。我们监听该事件,判断当前页面URL是否符合当前页面的路由规则。如果有差异,就调用window.location.reload()触发刷新。

代码

父页面核心代码

你可以参考 game.hullqin.cn 的网页源码,这是一个非常简洁的门户页面。

<div style="flex-grow:1;display:flex;flex-direction:column;justify-content:center">
  <a class=
<think>好的,我现在需要回答用户的问题:“使用 history.pushState() 和 replaceState() 是否会影响 history.go() 的效果?” 首先,我要回顾用户之前的对话。用户之前询问了关于javascript:history.go(-1)的问题,现在接着问关于pushState和replaceState是否影响history.go()的效果。这个问题属于浏览器历史记录管理的范畴,需要深入理解HTML5 History API的相关方法。 首先,我需要确认history.pushState()和replaceState()的作用。这两个方法是HTML5引入的,允许开发者在不重新加载页面的情况下修改浏览器的历史记录。pushState()会向历史记录栈中添加一个新的状态,而replaceState()则是替换当前的历史记录条目。这常用于单页应用(SPA)中,以实现无刷新页面导航。 接下来,用户想知道这两个方法是否会影响history.go()的效果。history.go()用于在历史记录中导航,参数是正数或负数,表示前进或后退的步数。比如history.go(-1)相当于后退一步。 接下来需要分析这两者的关系。当使用pushState或replaceState修改历史记录后,历史记录栈的结构会发生变化。例如,添加新的条目会增加历史记录的长度,替换条目则不会改变长度但会改变当前条目。因此,调用history.go()时,其导航的路径会受到这些修改的影响。 需要明确的是,虽然pushState和replaceState修改了历史记录栈,但它们并不会触发页面的重新加载,这是和传统导航的不同之处。然而,当用户使用history.go()导航到被这些方法修改过的条目时,浏览器会根据当前的历史记录栈进行跳转,但不会重新加载页面,除非条目中包含实际的页面加载(比如通过传统导航跳转的页面)。 另外,需要注意的是,使用pushState和replaceState时,虽然改变了URL,但页面的内容不会自动更新,这需要开发者通过监听popstate事件来手动处理内容的变化。当用户使用history.go()导航到由这些方法创建的历史条目时,会触发popstate事件,开发者可以在此事件中更新页面内容。 此外,pushState和replaceState可以添加或修改state对象,这个对象可以在popstate事件中被获取,用于恢复页面状态。因此,在SPA中,这些方法结合popstate事件处理,可以实现复杂的前端路由管理。 总结来说,pushState和replaceState会修改浏览器的历史记录栈,从而影响history.go()的行为,但实际的页面导航需要开发者配合popstate事件处理来实现内容的更新,而不会自动加载新页面。因此,回答用户的问题时,需要明确说明这种影响,并指出在SPA中的典型应用方式。 在回答时,还要注意用户可能存在的后续问题,比如如何结合这些方法实现路由管理,或者安全性问题,但当前问题仅关注对history.go()的影响,需保持回答的针对性。 需要确保回答结构清晰,分点说明影响,并给出示例,如使用pushState添加条目后,history.go(-1)的行为变化。同时,提醒用户注意popstate事件的处理,以正确更新页面内容。 最后,检查是否存在可能的误解,例如认为pushState会导致页面重新加载,需要明确说明它们不会触发页面加载,但会改变历史记录栈,从而影响history.go()的导航路径。</think>**是的,`history.pushState()` 和 `replaceState()` 会影响 `history.go()` 的效果**,因为它们直接操作浏览器的历史记录栈。以下是具体影响的说明: --- ### 1. **`pushState()` 的作用** - **功能**:向历史记录栈**添加新条目**,但不会触发页面加载或刷新。 - **对 `history.go()` 的影响**: - 新增的条目会成为历史记录的一部分,例如: ```javascript // 当前栈:[A](当前页) history.pushState({}, "", "/pageB"); // 栈变为:[A, B] history.go(-1); // 返回 A(因为栈中有 A → B) ``` - 如果多次调用 `pushState()`,历史栈长度增加,`history.go(-N)` 可以回退更多步骤。 --- ### 2. **`replaceState()` 的作用** - **功能**:**替换当前历史记录条目**,不增加栈长度。 - **对 `history.go()` 的影响**: - 当前条目被覆盖,但栈长度不变,例如: ```javascript // 原始栈:[A] history.replaceState({}, "", "/newA"); // 栈仍为:[A],但 A 的内容被替换为 newA history.go(-1); // 无法回退(因为栈长度未变,当前已是栈顶) ``` --- ### 3. **关键特性** - **不触发 `popstate` 事件**:调用 `pushState()` 或 `replaceState()` 时,**不会触发** `popstate` 事件。只有用户手动操作(如点击后退按钮)或调用 `history.go()`、`back()`、`forward()` 时才会触发。 - **`state` 对象传递**:可以通过 `pushState()` 的 `state` 参数存储数据,后续在 `popstate` 事件中通过 `event.state` 读取: ```javascript history.pushState({ page: "settings" }, "", "/settings"); window.addEventListener("popstate", (event) => { console.log(event.state); // 输出 { page: "settings" } }); ``` --- ### 4. **实际应用场景** - **单页应用(SPA)路由**:Vue Router、React Router 等框架通过 `pushState` 和 `replaceState` 实现无刷新页面切换。例如: ```javascript // Vue Router 内部逻辑简化 function navigate(path) { history.pushState({}, "", path); updateViewBasedOnPath(); // 手动更新页面内容 } ``` - **表单提交后保留状态**:提交表单后,用 `replaceState` 替换当前 URL,避免用户重复提交。 --- ### 5. **注意事项** - **安全性限制**:出于隐私保护,**无法通过 JavaScript 直接读取完整历史记录**,只能操作当前标签页的栈。 - **路径需同源**:`pushState` 和 `replaceState` 的 URL 必须与当前页面同源(Same-origin)。 --- ### 总结 - **会改变历史栈**:`pushState()` 增加条目,`replaceState()` 修改当前条目,均影响 `history.go()` 的导航范围。 - **需配合 `popstate` 事件**:开发者需监听此事件,手动更新页面内容,否则 URL 变化但页面不刷新。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hull Qin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值