thymeleaf 整合 pjax 无刷新跳转

article/2025/8/27 4:30:44

原文地址: http://www.linzichen.cn/article/1577881001718185984

在一些需要做 seo 优化的应用里,比如门户网站、博客论坛网站、商城商品页网站等,我们的数据常常采用 服务端渲染的方式来展现,目的是为了让爬虫更好的抓取到,从而在搜索引擎搜索时,可以搜到到我们自己的应用网站。

模板引擎

服务端渲染是在服务端通过模板引擎和其特定语法完成数据页面DOM的拼接,然后统一响应给浏览器。市面上存在许许多多五花八门的模板引擎。就以 Java 为例,在 JavaWeb 阶段就存在 JSP,到了 Springboot 又推荐 Thymeleaf,还有其他的如 FreeMarkervelocity等。每种引擎的语法、特性及优缺点这里就不再赘述,SpringBoot官方推荐使用 Thymeleaf,且本站也是基于此引擎搭建的,所以本文就以 Thymeleaf模板引擎为案例来演示。

thymeleaf 案例

先实现页面跳转效果:

demo.gif

公共组件抽取

每个页面都会引入一些公共的 css 或 js,还有一些公共的页面组件,比如上面案例中头部导航栏 和 底部栏,我们可以抽取出来,放在公共组件中。然后直接在页面中引入即可。

resources/templates/common 目录中,创建 fragment.html文件,可以用来存放公共组件。

fragment.png

fragment.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!-- 公共head -->
<head th:fragment="head(title, links, scripts)"><meta charset="UTF-8"><title th:replace="${title}"></title><th:block th:replace="${links}" /><script th:replace="${scripts}"></script><style>ul {overflow: hidden}li {list-style: none; margin-right: 20px;float: left}</style>
</head>
<body><!-- 公共header --><header th:fragment="header"><ul><li><a href="/view" data-pjax>首页</a></li><li><a href="/view/a" data-pjax>a页面</a></li><li><a href="/view/b" data-pjax>b页面</a></li></ul></header><!-- 公共footer  --><footer th:fragment="footer"><p> 这是 footer 部分 </p></footer>
</body>
</html>

页面创建

resources/templates/view 目录下,创建3个页面:index.html(首页)、a.html 和 b.html。

view.png

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="common/fragment :: head(~{::title}, ~{}, ~{})"><title>index页面</title>
</head>
<body><header th:replace="common/fragment :: header"></header><div class="content">index页面内容</div><footer th:replace="common/fragment :: footer"></footer>
</body>
</html>

其他页面同理,只需改下 title 标签和 <div class="content"> 中的内容即可。

路由控制

我们把负责控制页面路由的 controller 单独抽取出来,为了方便管理,给加一个统一的前缀 /view,这样可以在后期加监控或拦截器时,只需要针对此前缀的路由即可。

@Controller
@RequestMapping("/view")
public class PageController {@GetMapping(value = {"", "/{name}"})public ModelAndView page(@PathVariable(value = "name", required = false) String name) {if (StringUtils.isEmpty(name)) {name = "index" ;}return new ModelAndView("/view/" + name) ;}
}

因为此处的 @RequestMapping 中统一加了前缀 /view,所以在 fragment.htmla标签的链接会加上这个前缀,否则路由不到指定的页面。

假设项目运行在 9999 端口,此时启动项目,访问 localhost:9999/view 则会自动跳转到 index.html 。且点击a标签时,与上面案例效果一致。

案例不足的地方

上面案例虽实现了基本页面跳转,且也提取了公共的组件。但是在页面切换时,浏览器是重新加载的,这样会存在几个弊端:

1、公共静态资源重新请求,网页整体响应相对较慢。

2、公共数据服务端重复获取,增加后台服务器压力。

3、扩展性不足,无法定制化需求(比如加一个音乐播放器的功能,一直刷新体验不好)。

针对以上问题,我们希望在页面跳转时,让浏览器不再刷新,且每次请求时,服务器只响应我们想要的数据,即已经加载过的静态资源和公共数据,我们就不让其重新加载了。

解决方案?

前后分离,前端采用 router 跳转

我们知道现在前端框架 vue 或者 react 等都有 router 路由的概念。虽然路由的两种模式 hashhistory 均可以实现我们想要的效果,但它们属于 客户端渲染,即通过 js 实现数据与DOM的拼接,不利于我们一开始提到的 seo,有悖初衷。

后端 forward 转发

我们也可以在controller 层通过 servletforward 转发来控制页面跳转,但是此种方式不会改变浏览器地址,作为对外网站来说不友好,所以也不推荐此种方式。

pjax

pjax是一个jQuery插件,它通过ajax和pushState技术提供了极速的(无刷新ajax加载)浏览体验,并且保持了真实的地址、网页标题,浏览器的后退(前进)按钮也可以正常使用。

pjax的工作原理是通过ajax从服务器端获取HTML,在页面中用获取到的HTML替换指定容器元素中的内容。然后使用pushState技术更新浏览器地址栏中的当前地址。

以下两点原因决定了pjax会有更快的浏览体验:

1、不存在页面资源(js/css)的重复加载和应用;

2、如果服务器端配置了pjax,它可以只渲染页面局部内容,从而避免服务器渲染完整布局的额外开销。

具体细节使用这里不再赘述,详情可以参考其 github: JQuery-Pjax文档

pjax流程分析

1、在页面中,有些链接我们希望返回的是局部页面,有些则是做其他的处理,所以我们需要把 返回局部页面a标签上,给加一个标识(例如 data-pjax),标注此链接是交给 pjax 来管理。

<a href="xxx" data-pjax> pjax链接 </a>
<a href="xxx"> 普通链接 </a>

2、我们希望在每个页面中,存在一个容器 .content,来存放本页面的主要内容。此容器也是 pjax 请求过来时,我们响应给它的局部html内容。

content.png

2、我们希望在每个页面中,提供一个容器 #pjax-container,用于展示 pjax 请求回来的其他局部页面。在没有 pjax 请求前,此容器中展示自己页面中的 .content 内容。

pjax.png

关键代码实现

pjax 管理 a标签

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery.pjax/2.0.1/jquery.pjax.min.js"></script><li><a href="/view" data-pjax>首页</a></li>
<li><a href="/view/a" data-pjax>a页面</a></li>
<li><a href="/view/b" data-pjax>b页面</a></li><div id="pjax-container" ><div th:fragment="content">这是index页面的内容</div>
</div><script type="text/javascript">// 管理具有 data-pjax 属性的 a标签,把a请求回来的内容,填充到本页面的#pjax-container中$(document).pjax('a[data-pjax]', '#pjax-container');
</script>

controller部分页面

@GetMapping("/a")
public ModelAndView a () {// 返回a页面中 th:fragment 为 content 的内容String fragment = "/view/a::content" ;return new ModelAndView(fragment) ;
}

效果

我们发现,我们在点击 a 标签后,页面没有刷新,且只返回了a 页面的局部内容,可以达到我们预期的效果。

pjax.gif

遗留问题

在 controller 层中,对于a页面,只返回了 th:fragment="content" 的内容。如果此链接是通过 pjax 访问的,那么正好是我们想要的效果,只返回局部页面。但如果用户一开始就访问的是 a 页面,会发生什么情况?

答案是仍然只返回 th:fragment 中的内容。

a.png

因为我们没有对请求做判断,我们需要判断请求是否是来自 pjax 请求,如果是,我们只返回部分页面。如果不是,我们需要返回整个 a 页面,包含 导航栏和底部栏。

拦截器判断请求是否是 pjax 请求

针对上面遗留的问题,我们只需要判断请求是浏览器第一次加载的,还是通过点击站内链接发起的 pjax 。我们发现,pjax 在发送请求时,会多携带两个请求头信息:

header.png

所以我们只需要在拦截器中,判断请求头是否包含此请求头就好了。如果包含,就只返回部分页面,否则就返回整个页面:

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {Boolean pjax = Boolean.parseBoolean(request.getHeader("X-PJAX")) ;if(pjax) {modelAndView.setViewName(modelAndView.getViewName() + "::content");}
}

加上拦截器判断之后,此时我们的跳转就一切正常了。

如图:

pjaxFinal.gif

项目代码

目录结构

目录.png

fragment.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!-- 公共head -->
<head th:fragment="head(title, links, scripts)"><meta charset="UTF-8"><title th:replace="${title}"></title><th:block th:replace="${links}" /><script th:replace="${scripts}"></script><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/jquery.pjax/2.0.1/jquery.pjax.min.js"></script><style>ul {overflow: hidden}li {list-style: none; margin-right: 20px;float: left}</style><script type="text/javascript">// 管理具有 data-pjax 属性的 a标签,把a请求回来的内容,填充到本页面的#pjax-container中$(document).pjax('a[data-pjax]', '#pjax-container');</script>
</head>
<body><!-- 公共header --><header th:fragment="header"><ul><li><a href="/view" data-pjax>首页</a></li><li><a href="/view/a" data-pjax>a页面</a></li><li><a href="/view/b" data-pjax>b页面</a></li></ul></header><!-- 公共footer  --><footer th:fragment="footer"><p> 这是 footer 部分 </p></footer>
</body>
</html>

index.html(a/b.html一样)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="common/fragment :: head(~{::title}, ~{}, ~{})"><title>index页面</title>
</head>
<body><header th:replace="common/fragment :: header"></header><div id="pjax-container"><div th:fragment="content">index页面内容</div></div><footer th:replace="common/fragment :: footer"></footer>
</body>
</html>

PageController

@Controller
@RequestMapping("/view")
public class PageController {@GetMapping(value = {"", "/{name}"})public ModelAndView page(@PathVariable(value = "name", required = false) String name) {if (StringUtils.isEmpty(name)) {name = "index" ;}return new ModelAndView("/view/" + name) ;}
}

InterceptorConfig

@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {@Autowiredprivate PjaxInterceptor pjaxInterceptor ;private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {"classpath:/META-INF/resources/", "classpath:/resources/","classpath:/static/", "classpath:/public/" };@Overrideprotected void addInterceptors(InterceptorRegistry registry) {String[] interceptPathList = new String[] {"/view/**"};registry.addInterceptor(pjaxInterceptor).addPathPatterns(interceptPathList) ;}@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/**").addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS) ;}
}

PjaxInterceptor

@Component
public class PjaxInterceptor implements HandlerInterceptor {@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {Boolean pjax = Boolean.parseBoolean(request.getHeader("X-PJAX")) ;if(pjax) {modelAndView.setViewName(modelAndView.getViewName() + "::content");}}
}

http://chatgpt.dhexx.cn/article/VaoXX8DO.shtml

相关文章

php pjax案例,jQuery pjax简单示例汇总

pjax 是一个jQuery插件&#xff0c;它使用 ajax 和 pushState 来实现快速的浏览体验&#xff0c;包括真正的固定链接&#xff0c;页面标题和工作返回按钮。本文主要和大家分享jQuery pjax简单示例汇总&#xff0c;希望能帮助到大家。 ajax缺点是破坏了浏览器的前进后退&#xf…

pjax php,php整合pjax(pushstate+ajax)实现无刷新页面

PJAX效果 通过url可以跟踪ajax的动态加载内容。这种技术尤其在two step view布局的视图中有很大的好处。无刷新加载页面&#xff0c;意味着响应速度和用户体验得到了极大的提升&#xff0c;在静态脚本和通用模块比较多的情况下&#xff0c;最大程度上节省了重用部分的开销。应用…

html5 pjax,pjax——页面无刷新跳转

pjax虽然不是什么新的技术&#xff0c;然而本人是最近才发现这个比较牛叉的技术。下面是对pjax的介绍&#xff1a; pjax是在HTML5里面引用的新技术&#xff0c;是对ajax pushState的封装&#xff0c;是实现无刷新ajax加载并解决浏览器前进和后退问题的一个开源实现。同时支持了…

java pjax_(转)PJAX的实现与应用

一、前言 web发展经历了一个漫长的周期&#xff0c;最开始很多人认为Javascript这们语言是前端开发的累赘&#xff0c;是个鸡肋&#xff0c;那个时候人们还享受着从一个a链接蹦到另一个页面的web神奇魔术。后来随着JavaScript的不断更新换代&#xff0c;他的功能不仅仅是为网页…

html5 pjax,关于PJAX局部刷新

前言部分 本教程最先来自鬼少博客&#xff0c;后论坛有人补充搜索和评论&#xff0c;然后又被各种转载&#xff0c;转完甚至还有阉割现象&#xff0c;导致会出现各种问题,于是&#xff0c;我这里再重发一次。虽然注释很清楚&#xff0c;但是&#xff0c;还有很多人看不懂&#…

java pjax_pjax简单实例

ajax缺点是破坏了浏览器的前进后退&#xff0c;因为ajax的请求不会留在历史记录中。pjax就不一样了&#xff0c;pjax被解释成ajaxpushState的封装&#xff0c;因为它把ajax的请求写入历史记录&#xff0c;并反映在地址栏&#xff0c;这样用户就能愉快地使用前进后退了。pjax有好…

Typecho开启全站Pjax

原文地址&#xff1a;Typecho开启全站Pjax 前言 因为上次更新后加入民音乐插件&#xff0c;但是有个问题就是在页面跳转的时候由于页面已经刷新了&#xff0c;所以音乐就不会继续播放了&#xff0c;就想着去引入Pjax来解决这个问题&#xff0c;同时引入pjax后比较直观的改变就…

pjax使用小结

前言 上周看到一篇文章在分析简书 我的主页 页面 3 个 tab 页切换的 bug&#xff0c;起先以为是寻常的样式 bug 而已没怎么在意&#xff0c;后来在文章中看到 pjax 这个术语&#xff0c;长得和 ajax 有点像&#xff0c;遂去了解了下。 简介 虽然传统的 ajax 方式可以异步无刷新…

网站访问速度优化之pjax

pjax 是 ajax 和 pushState 的结合&#xff0c;它是一个 jQuery 插件。它通过 ajax 从服务器端获取 HTML 文件&#xff0c;在页面中用获取到的HTML替换指定容器元素中的内容。然后使用 pushState 技术更新浏览器地址栏中的当前地址&#xff0c;并且保持了真实的地址、网页标题&…

idea热更新

配置idea热更新 第一步&#xff1a;下载插件 JRebel idea-file-settings-plugins搜JRebel 点击installed下载 我这里已经下载好。 第二步&#xff1a;配置GUID 点击jrebel Activation&#xff0c;开始配置 第一行是服务器地址&#xff1a;https://jrebel.qekang.com/{GUID} G…

webpack和vue热更新

目录 webpack一些概念介绍 webpack热更新流程 1. 启动阶段 ①->②->A->B 2. 更新阶段 ①->②->③->④ vue的组件热更新模块 总结 提到热更新&#xff0c;首先我们要有一个概念&#xff1a;Vue有热更新模块&#xff0c;而webpack也有它的HRM模块&#x…

JAVA实现代码热更新

JAVA实现代码热更新 引言类加载器实现热更新思路多种多样的加载来源SPI服务发现机制 完整代码类加载器共享空间机制Tomcat如何实现JSP的热更新Spring反向访问用户程序类问题补充细节推荐资源 引言 本文将带领大家利用Java的类加载器加SPI服务发现机制实现一个简易的代码热更新…

cordova打包app热更新问题

定义&#xff1a; 基于 cordova 框架能将web应用 (js, html, css, 图片等) 打包成 App。当 App 在终端上安装后&#xff0c;不需要重新下载app&#xff0c;实现内壳更新。 原理&#xff1a;1.在项目根目录的config.xml文件中添加指向服务器的地址 2.在www目录中添加chcp.json配…

【热更新】游戏热更新方案

游戏热更新方案 热更新演化热更新方案【1】 进程切换1.1 利用fork、exec切换1.2 利用网关切换1.3 微服务- 进程切换注意要点 【2】 动态库替换【3】 脚本语言热更新热更新探究最简单的实现热更的方法最简单的实现热更的方法的局限性热更新全局替换模块方法的局限性 工程实现1. …

Addressable热更新

文章目录 前提配置代码实现 前提配置 &#xff08;1&#xff09;勾选AddressableAssetSettings设置的Disable Catalog Update On Startup选项 &#xff08;2&#xff09;相应的热更戏资源分组配置&#xff08;注&#xff1a;此文采用的是动态资源更新&#xff09; Can Change …

Nacos配置热更新的4种方式、读取项目配置文件的多种方式,@value,@RefreshScope,@NacosConfigurationProperties

nacos实现配置文件的热更新&#xff0c;服务不用重启即可读取到nacos配置中心修改发布后的最新值&#xff0c;spring&#xff0c;springboot项目读取本地配置文件的各种方式&#xff1b;文章中介绍了一下注解的使用&#xff1a;NacosConfigurationProperties&#xff0c;NacosP…

Unity 热更新技术 | (一) 热更新的基本概念原理及主流热更新方案介绍

&#x1f3ac; 博客主页&#xff1a;https://xiaoy.blog.csdn.net &#x1f3a5; 本文由 呆呆敲代码的小Y 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;Unity系统学习专栏 &#x1f332; 游戏制作专栏推荐&#xff1a;游戏制作 &…

JAVA热更新

引言 知识储备先看这篇文章&#xff1a;JAVA Instrument 在这个案例中我们会利用Instrument机制实现一个简单的热更新案例。 总体来说&#xff0c;步骤如下&#xff1a; 创建一个带premain方法的jar包。这个方法定时检测某个文件然后进行热更新。命令行启动业务类时使用参数…

热更新 深度解析

APP热更新方案 为什么要做热更新 当一个App发布之后&#xff0c;突然发现了一个严重bug需要进行紧急修复&#xff0c;这时候公司各方就会忙得焦头烂额&#xff1a;重新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。 重点是还会有原来的版本遗留…

webpack热更新

什么是模热更新&#xff1f;有什么优点 模块热更新是webpack的一个功能&#xff0c;它可以使得代码修改之后&#xff0c;不用刷新浏览器就可以更新。 在应用过程中替换添加删出模块&#xff0c;无需重新加载整个页面&#xff0c;是高级版的自动刷新浏览器。 优点&#xff1a…