当前位置: 首页 > Web前端 > HTML5

【极致用户体验】在多页面应用中,什么时候使用网页中的“后退”按钮,什么时候使用history.back,什么时候使用replaceState?

时间:2023-04-06 00:17:26 HTML5

我是公众号线下派对游戏的作者HullQin(欢迎关注公众号,发加微信,交友),转载本文需作者HullQin授权。我独立开发了《联机桌游合集》,这是一个网页,在这里你可以轻松地和朋友一起玩网络游戏,五子棋等游戏,不收费,也没有广告。还为GameJam2022开发了《Dice Crush》,喜欢的话可以关注我HullQin哦~有空我会分享制作游戏的相关技术。背景上一篇《网页里的「返回」应该用 history.back 还是 push ?》演示了单页面应用程序(Single-PageApplication,简称SPA)如何实现网页中的“后退”按钮。本文将演示一个多页面应用程序(Multi-PageApplication,简称MPA)如何在网页中实现“后退”按钮。什么是“极致用户体验”我上面提到网站应该有一个页面层次结构:它是一个树状结构,每个页面和模块都划分得很清楚。如果要追求极致的用户体验,当用户在浏览器中点击“前进”或“后退”时,应该遵循这样的规则:点击浏览器的“前进”按钮(前进,右箭头)只允许相邻页面级别,从左跳到右。单击浏览器的“后退”按钮(后退,左箭头)只允许相邻的页面级别从右向左返回。实施方案要实现这样一条规则,开发者必须控制浏览器的历史堆栈:当用户进入更深层次的页面时,浏览器的历史堆栈会加1。当用户返回到较浅的页面层次时,浏览器的历史堆栈会加1。historystack减1。但是,当historystack不能减1时,historystack的个数可以保持不变。让我解释一下,开发人员如何控制历史堆栈?历史栈什么时候加一?当我们调用history.pushState()时,会在浏览器历史栈中添加一条新的历史记录,主要存储URL等信息。此时,用户点击“BrowserBack”和“BrowserForward”,重复跳转“PreviousPage”和“CurrentPage”。什么时候历史堆栈减一?当我们调用history.back()时,我们可以将浏览器历史堆栈减一。其实严格来说并不是负一,而是页面返回到上一条记录,相当于用户点击了“浏览器后退”按钮。有时候点击“网页后退”按钮,并不能直接调用history.back。为什么?如果调用history.back()会返回到其他界面(或者用户直接打开了我们的某个页面,没有之前的历史记录,“浏览器后退”按钮也是灰色的),即调用history.back()不能返回我们自己网站的上一级页面,应该调用history.replaceState()跳转到上一级页面。请注意,不能使用history.push。如果你用push,就违反了我们的原则。那时点击“浏览器后退”从左到右导航,这违反了我们的网站页面层次结构。回过头来看单页应用的方案,父页面只要跳转到子页面,就带着一个“标识”,告知子页面,跳转的来源是你自己的父亲。子页面会知道,因为页面的“网页后退”按钮可以直接触发history.back()返回。如果子页面没有“logo”,说明父页面没有跳转到子页面,无法通过history.back()返回父页面。必须通过history.replaceState()去父页面,去的时候不能带“logo”,因为子页面不是父页面的parent。跳跃时的“识别”可以通过history.pushState()中的状态来实现。不得使用URL中的参数来实现。由于网址太容易伪造,用户可能会点击收藏,复制网址,带上logo。但是状态绝对是够隐蔽的。多页面应用解决问题描述我的父页面game.hullqin.cn和子页面game.hullqin.cn/wzq部署了两套前端代码,分别是MPA。子页面有一个“游戏列表”按钮,相当于我的“网页后退”按钮。我希望这两个页面符合站点的页面层次结构:如果hittory.back()可以用于返回主页,则使用它。如果hittory.back()不能返回首页,使用history.replaceState()。难点1、直接调用history.pushState()可以传递stateflag,但是这样只会修改url,不会触发浏览器刷新,网页还是停留在父页面。直接调用window.location.href='game.hullqin.cn/wzq'会刷新浏览器,但不能传递stateflag。可能还需要使用sessionStorage方案来保存和传递“身份”,但这引入了更高的复杂度,因为它与历史栈无关,我们必须在sessionStorage中存储一些路由信息才能传递“正确识别”。解决难点1,先调用history.pushState(),传递stateflag,然后调用window.location.reload()触发刷新。这样会为下一页保持状态。难点2如果我们通过调用history.pushState()来增加浏览器历史栈,那么当我们调用history.back()时,页面不会刷新,只会改变URL。也许你只是说:和以前一样,调用history.back()然后调用window.location.reload()触发刷新,不就解决了吗?但是这里还有一点:当用户点击“浏览器后退”按钮时,只会改变URL,而且页面不会刷新,虽然url已经是首页,但是界面还是在子页面game.hullqin.cn/wzq。同样,当用户点击父页面的“浏览器前进”按钮进入子页面game.hullqin.cn/wzq时,只会改变URL,不会刷新页面。解决难点2监听window.onpopstate事件,当用户点击“浏览器后退”按钮或“浏览器前进”按钮时会触发该事件。我们监听这个事件来判断当前页面的URL是否符合当前页面的路由规则。如果有差异,调用window.location.reload()触发刷新。代码父页面的核心代码可以参考game.hullqin.cn网页的源代码,这是一个非常简洁的门户页面。UNO游戏/a>五子棋

注意:调用history.pushState()时,传递的状态标识为:{key:Math.random().toString(36).substring(2,10),usr:{keepSession:true},}这是为了遵守子页面React-Router状态的规范,它需要包含一个随机字符串键,标记一个会话,并将其存储在usr开发者自定义数据中正如我在上一篇文章中提到的,我使用名称keepSession来标识子页面来自我的父亲。子页面核心代码子页面我使用了React框架和React-Router。“网页返回”按钮的核心逻辑如下:import{Link,useLocation,useNavigate}from'react-router-dom';functionBackLink(props:BackLinkProps){const{to,children,className,}=props;constnavigate=useNavigate();const{状态}=useLocation();constkeepSession=state.keepSession;consthandleClick=(event:MouseEvent)=>{if(event.button!==0)返回;如果(事件.ctrlKey||event.metaKey||event.shiftKey||event.altKey)返回;事件.preventDefault();如果(keepSession){导航(-1);}elseif(window.history.length===1){navigate(to,{replace:true});}else{navigate(to,{replace:true});//以如下方式刷新浏览器的“转发”记录,避免通过“转发”进入非预期的页面navigate(to);导航(-1);}};return({children});}//还有如下逻辑,可以写在另外的js文件中,也可以直接写在html中:窗口.addEventListener('popstate',()=>{if(!window.location.pathname.startsWith("/wzq"))window.location.reload();});也许你会好奇if(event.button!==0)return;如果(event.ctrlKey||event.metaKey||event.shiftKey||event.altKey)返回;这两行代码,欢迎阅读文章:《你的 Link Button 能让用户选择新页面打开吗?》写在最后我是HullQin,公众号线下派对游戏的作者(欢迎关注公众号,发送加微信,交个朋友).转载本文前必须获得作者HullQin的授权。我独立开发了《联机桌游合集》,这是一个网页,在这里你可以轻松地和朋友一起玩网络游戏,五子棋等游戏,不收费,也没有广告。还为GameJam2022开发了《Dice Crush》,喜欢的话可以关注我HullQin哦~有空我会分享制作游戏的相关技术。