pjax使用小结

article/2025/8/27 6:22:54

前言


上周看到一篇文章在分析简书 我的主页 页面 3 个 tab 页切换的 bug,起先以为是寻常的样式 bug 而已没怎么在意,后来在文章中看到 pjax 这个术语,长得和 ajax 有点像,遂去了解了下。

简介


虽然传统的 ajax 方式可以异步无刷新改变页面内容,但无法改变页面 URL,因此有种方案是在内容发生改变后通过改变 URLhash 的方式获得更好的可访问性(如 https://liyu365.github.io/BG-UI/tpl/#page/desktop.html),但是 hash 的方式有时候不能很好的处理浏览器的前进、后退,而且常规代码要切换到这种方式还要做不少额外的处理。而 pjax 的出现就是为了解决这些问题,简单的说就是对 ajax 的加强。

pjax 结合 pushState 和 ajax 技术, 不需要重新加载整个页面就能从服务器加载 Html 到你当前页面,这个 ajax 请求会有永久链接、title 并支持浏览器的回退/前进按钮。

pjax 项目地址在 https://github.com/defunkt/jquery-pjax 。 实际的效果见: http://pjax.herokuapp.com 没有勾选 pjax 的时候点击链接是跳转的, 勾选了之后链接都是变成了 ajax 刷新(实际效果如下图的请求内容对比)。

不使用pjax

 

使用pjax

优点:

  • 减轻服务端压力

按需请求,每次只需加载页面的部分内容,而不用重复加载一些公共的资源文件和不变的页面结构,大大减小了数据请求量,以减轻对服务器的带宽和性能压力,还大大提升了页面的加载速度。

  • 优化页面跳转体验

常规页面跳转需要重新加载画面上的内容,会有明显的闪烁,而且往往和跳转前的页面没有连贯性,用户体验不是很好。如果再遇上页面比较庞大、网速又不是很好的情况,用户体验就更加雪上加霜了。使用pjax后,由于只刷新部分页面,切换效果更加流畅,而且可以定制过度动画,在等待页面加载的时候体验就比较舒服了。

缺点:

  • 不支持一些低版本的浏览器(如IE系列)

pjax使用了pushState来改变地址栏的url,这是html5中history的新特性,在某些旧版浏览器中可能不支持。不过pjax会进行判断,功能不适用的时候会执行默认的页面跳转操作。

  • 使服务端处理变得复杂

要做到普通请求返回完整页面,而pjax请求只返回部分页面,服务端就需要做一些特殊处理,当然这对于设计良好的后端框架来说,添加一些统一处理还是比较容易的,自然也没太大问题。另外,即使后台不做处理,设置pjax的fragment参数来达到同样的效果。

综合来看,pajx 的优点很强势,缺点也几乎可以忽略,还是非常值得推荐的,尤其是类似博客这种大部分情况下只有主体内容变化的网站。关键它使用简单、学习成本小,即时全站只有极个别页面能用得到,尝试下没什么损失。pjaxgithub 主页介绍的已经很详细了,想了解更多可以看下源码。

用法


  1. 引入 jquery 和 jquery.pjax.js
  2. 注册事件

 

/*** 方式一 按钮父节点监听事件** @param selector  触发点击事件的按钮* @param container 展示刷新内容的容器,也就是会被替换的部分* @param options   参数*/
$(document).pjax(selector, [container], options);// 方式二 直接对按钮监听,可以不用指定容器,使用按钮的data-pjax属性值查找容器
$("a[data-pjax]").pjax();// 方式三 常规的点击事件监听方式
$(document).on('click', 'a', $.pjax.click);
$(document).on('click', 'a', function(event) {var container = $(this).closest('[data-pjax-container]');$.pjax.click(event, container);
});// 下列是源码中介绍的其他用法,由于本人暂时没有那些需求暂时没深究,有兴趣的各位自己试试看哈
// 表单提交
$(document).on('submit', 'form', function(event) {var container = $(this).closest('[data-pjax-container]');$.pjax.submit(event, container);
});
// 加载内容到指定容器
$.pjax({ url: this.href, container: '#main' });
// 重新当前页面容器的内容
$.pjax.reload('#container');

options默认参数说明


参数名默认值说明
timeout650ajax 超时时间(单位 ms ),超时后会执行默认的页面跳转,所以超时时间不应过短,不过一般不需要设置
pushtrue使用 window.history.pushState 改变地址栏 url( 会添加新的历史记录 )
replacefalse使用 window.history.replaceState 改变地址栏 url( 不会添加历史记录 )
maxCacheLength20缓存的历史页面个数( pjax 加载新页面前会把原页面的内容缓存起来,缓存加载后其中的脚本会再次执行 )
version 是一个函数,返回当前页面的pjax-version,即页面中 <meta http-equiv="x-pjax-version"> 标签内容。使用 response.setHeader("X-PJAX-Version", "") 设置与当前页面不同的版本号,可强制页面跳转而不是局部刷新。
scrollTo0页面加载后垂直滚动距离( 与原页面保持一致可使过度效果更平滑 )
type"GET"ajax 的参数,http 请求方式
dataType"html"ajax 的参数,响应内容的 Content-Type
container 用于查找容器的 CSS 选择器,[container] 参数没有指定时使用
urllink.href要跳转的连接,默认 a 标签href 属性
targetlinkpjax 事件参数 erelatedTarget 属性,默认为点击的 a 标签
fragment 使用响应内容的指定部分( CSS 选择器 )填充页面,服务端不进行处理导致全页面请求的时候需要使用该参数,简单的说就是对请求到的页面做截取

除了上述参数外,ajax 的一些参数也是可以设置在这里的,不过一般没什么必要。

 

// ajax 最终参数: 
options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options);

pjax失效情况


会有一些情况导致 pjax 失效,下面结合源码分析下(省略部分无关代码)

 

function handleClick(event, container, options) {...// 1. 点击事件的事件源不是a标签。使用a标签可以做到对旧版本浏览器的兼容,所以不建议使用其他标签注册事件if (link.tagName.toUpperCase() !== 'A')throw "$.fn.pjax or $.pjax.click requires an anchor element"// 2. 使用鼠标滚轮点击(新标签页打开)// 点击超链接的同时按下Shift、Ctrl、Alt和Meta(在Windows键盘中是Windows键,在苹果机中是Cmd键)// 作用分别代表新窗口打开、新标签打开(不切换标签)、下载、新标签打开(切换标签)if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)return// 3. 跨域(网络通讯协议,域名不一致)if (location.protocol !== link.protocol || location.hostname !== link.hostname)return// 4. 当前页面的锚点定位if (link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location))return// 5. 已经阻止元素发生默认的行为(url跳转)if (event.isDefaultPrevented())return...var clickEvent = $.Event('pjax:click')$(link).trigger(clickEvent, [opts])// 6. pjax:click事件回调中已经阻止元素发生默认的行为(url跳转)if (!clickEvent.isDefaultPrevented()) {pjax(opts)event.preventDefault()// 阻止url跳转$(link).trigger('pjax:clicked', [opts])}
}

除了上述情况之外,还有下列几种情况:

  • ajax 请求失败,或者 timeout 后请求被中止
  • 当前页面的 X-PJAX-Version 和请求的新页面版本不一致
  • 请求得到完整的页面(包含 html 标签)却没设置 fragment 参数

事件


1. 点击链接后触发的一系列事件, 除了 pjax:clickpjax:clicked 的事件源是点击的按钮,其他事件的事件源都是要替换内容的容器。可以在 pjax:start 事件触发时开始过度动画,在 pjax:end 事件触发时结束过度动画。

事件名支持取消参数说明
pjax:clickoptions点击按钮时触发。可调用 e.preventDefault(); 取消pjax
pjax:beforeSendxhr, optionsajax 执行 beforeSend 函数时触发,可在回调函数中设置额外的请求头参数。可调用 e.preventDefault(); 取消 pjax
pjax:start xhr, optionspjax 开始(与服务器连接建立后触发)
pjax:send xhr, optionspjax:start 之后触发
pjax:clicked optionsajax 请求开始后触发
pjax:beforeReplace contents, optionsajax 请求成功,内容替换渲染前触发
pjax:success data, status, xhr, options内容替换成功后触发
pjax:timeoutxhr, optionsajax 请求超时后触发。可调用 e.preventDefault(); 继续等待 ajax 请求结束
pjax:errorxhr, textStatus, error, optionsajax 请求失败后触发。默认失败后会跳转 url,如要阻止跳转可调用 e.preventDefault();
pjax:complete xhr, textStatus, optionsajax 请求结束后触发,不管成功还是失败
pjax:end xhr, optionspjax 所有事件结束后触发
  • 注意:
    pjax:beforeReplace 事件前 pjax 会调用 extractContainer 函数处理页面内容,即以 script[src] 的形式引入的 js 脚本不会被重复加载,有必要可以改下源码

2. 浏览器前进/后退导航时触发的事件(暂时没做过多研究)

事件名参数说明
pjax:popstate 页面导航方向: 'forward'/'back'(前进/后退)
pjax:startnull, optionspjax 开始
pjax:beforeReplacecontents, options内容替换渲染前触发,如果缓存了要导航页面的内容则使用缓存,否则使用 pjax 加载
pjax:endnull, optionspjax 结束

服务端配置


我的项目是 Spring MVC + velocity 的组合,这里就以此为例子,其他语言和框架的服务端可以参考下这里的思路。
项目中使用的视图解析器是 org.springframework.web.servlet.view.velocity.VelocityLayoutViewResolver 这个类,好处是可以使用模版技术,每个页面可以只写主体内容,公共部分统一写在模版里面,是不是和 pjax 绝配哈!pjax.js 默认会在请求头加入 X_PJAX 字段,并置为 true,所以以此来判断是否 pjax 请求。对于普通的请求使用常规的模版,pjax 请求则使用空模版或者特定的模版。

  • 常规模版内容:

 

<!doctype html>
<html>#set($basePath = "screen/contain")<head><meta http-equiv="x-pjax-version" content="$!{X-PJAX-Version}"/>#parse("$basePath/html-head.vm")</head><body><section id="container">#parse("$basePath/frame-head.vm")#parse("$basePath/frame-left.vm")<section id="main-content"><section class="wrapper">$screen_content ##页面内容</section></section>#parse("$basePath/frame-bottom.vm")</section></body>
</html>
  • 添加 SpringMVC 中的 Interceptor 拦截器,用于后端渲染前插入 pjax 处理

 

public class PjaxInterceptor extends HandlerInterceptorAdapter {@Value("${X-PJAX-Version}")private String X_PJAX_VERSION;/*** Controller 方法调用之后,页面渲染前执行* * @param request* @param response* @param handler* @param modelAndView* @throws Exception*/public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {if (modelAndView != null) {boolean isPajx = Boolean.parseBoolean(request.getHeader("X-PJAX"));// 值为true表示pjax请求,这是重点ModelMap model = modelAndView.getModelMap();model.addAttribute("X-PJAX-Version", X_PJAX_VERSION);// 设置当前页面的pjax版本if (isPajx) {model.addAttribute("layout", "layout_pjax.vm");// 指定pjax请求时使用的模版// 在vm页面中通过 #set($layout = 'xxx.vm') 的方式指定模版response.setHeader("X-PJAX-Version", X_PJAX_VERSION);// 响应内容的pjax版本,有新模版发布时,通过配置文件修改版本来强制页面刷新}}}
}
  • xml 配置

 

<mvc:interceptors><mvc:interceptor><mvc:mapping path="/**"/><bean id="pjaxInterceptor" class="xxx.PjaxInterceptor"/></mvc:interceptor>
</mvc:interceptors>
  • pjax 请求模版页面:layout_pjax.vm

 

<title>$!{title}</title>
$screen_content

模版中使用 title 标签,这样执行 pjax 请求时不仅地址栏 url 会变化,而且浏览器标签的标题内容也会变化。

针对没有服务端处理的方案如下:

 

// fragment一般同container一致
$(document).pjax('a[data-pjax]', '#main-content .wrapper', {fragment: '#main-content .wrapper'});

插件伴侣——NProgress


比较漂亮的一款进度条插件,用法十分简单,很适合做pjax的过度动画,详细用法在该项目 github 上有介绍

NProgress

  • 示例:

 

$(document).on('pjax:start', NProgress.start).on('pjax:end', NProgress.done);

结语


虽然个人还是比较喜欢造轮子(有成就感),不怎么喜欢用插件(一般插件使用复杂,文档少学习成本大,还不如自己写),但看了 pjax 的源码后感觉真要自己也使用 pushState + ajax 的方式简单的实现它的功能,还是要踩不少坑的,所以为什么要放着这么个易用又精致的小轮子不用呢?我的项目是一个管理系统,统一的 左侧菜单 + 右侧table 的布局,每个页面都需要一个独立访问的 url,非常适合使用 pjax。由于使用的 velocity 模版技术,集成 pjax 就是分分钟的事,不仅对原先的代码完全没影响,还提升了加载速度,页面过度效果更好,再用上了 NProgress,感觉逼格又上升不少,哈哈。


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

相关文章

网站访问速度优化之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…

electron 热更新

1. electron自带的整体更新方式 &#xff08;全量更新&#xff09; 这种方式为electron官方的升级更新方式&#xff0c;主要是通过主进程中的autoUpdater模块进行检测升级更新的&#xff0c;此方式也是大家常见的大多数electron应用程序的更新方式。 检测到新版本后从服务器拉…

uniApp实现热更新

热更新 热更新是开发中常见且常用的一种软件版本控制的方式&#xff0c;在uniapp进行使用热更新将软件实现更新操作 思路: 服务器中存储着最新版本号&#xff0c;前端进行查询可以在首次进入应用时进行请求版本号进行一个匹对如果版本号一致则不提示&#xff0c;反之则提示进行…

Android热更新详解

一 前言介绍 正好最近又看到热更新&#xff0c;对以前Android 热修复核心原理&#xff1a;ClassLoader类加载机制做了点补充。 从16年开始开始&#xff0c;热修复技术开始在安卓界流行&#xff0c;它以classloader类加载机制为核心&#xff0c;可以不发布新版本就修复线上 bu…

热更新原理

对于热更新的问题就是了解两个点的问题&#xff1a; 如何加载补丁包&#xff0c;也就是如何加载dex 文件的过程&#xff08;dex是补丁包&#xff0c;更改的文件都在补丁包中&#xff09;修复后的类如何替换掉旧的类 通过这篇文章给大家介绍下我理解的热更新的逻辑&#xff0c…

Cocos Creator 3.x 热更新

前言&#xff1a;游戏做热更新 是基本需求&#xff1b; 好在 cocos-creator 已经为我们做好了方案&#xff0c;相对于 U3D 的热更新方案来说&#xff0c;使用起来很简便&#xff01;&#xff0c;不用关注很多细节 本文使用的是 cocos-creator 3.5.2 版本 官方文档 &#xff1…

热更新原理及实践注意

首先要说明几个概念&#xff0c;不要混用&#xff0c;热部署&#xff0c;热加载&#xff1b; 热部署&#xff1a;就是已经运行了项目,更改之后,不需要重新tomcat,但是会清空内存,重新打包,重新解压war包运行&#xff0c;可能好处是一个tomcat多个项目,不必因为tomcat停止而停止…

热更新你都知道哪些?

热更新系列目录 热更新你都知道哪些&#xff1f;热更新Sophix的爬坑之路腾讯热更新Tinker的故事阿里热更新Sophix的故事 Android热更新 前言1. 什么是热更新&#xff1f;2. 主流热更新方案3. 腾讯系热更新4. 阿里系热更新总结 博客创建时间&#xff1a;2020.05.16 博客更新时间…

热更新技术简易原理及技术推荐

为了照顾萌新童鞋&#xff0c;最开始还是对热更新的概念做一个通俗易懂的介绍。 热更新用通俗的讲就是软件不通过应用商店的软件版本更新审核&#xff0c;直接通过应用自行下载的软件数据更新的行为。在用户下载安装App之后&#xff0c;打开App时遇到的即时更新&#xff0c;是…