英文文档地址:《Pjax》
本人水平有限,汉化依靠机翻完成 ,如有错误,还望读者能够在评论中斧正。
原文代码中的var
关键字均被我替换为const
或let
。
Pjax
在任何网站上轻松启用快速 AJAX 导航(通过使用 pushState() + XHR)
Pjax 是一个独立的 JavaScript 模块,它使用AJAX(XmlHttpRequest)和pushState()来提供快速浏览的体验。
它允许你完全改变标准网站(服务器端生成的或静态的)的用户体验,使用户感觉他们在浏览一个应用程序,特别是对于那些低带宽连接的用户,用户体验的提升会更加明显。
不再需要完整的页面重新加载,不再需要多个 HTTP 请求。
Pjax 不依赖其他库(包括但不限于 jQuery),它完全是用 vanilla JS 编写的。
安装
你可以直接链接到该JS文件:
1
<script src="https://fastly.jsdelivr.net/npm/pjax@VERSION/pjax.js"></script>
或者其压缩版JS文件:
1
<script src="https://fastly.jsdelivr.net/npm/pjax@VERSION/pjax.min.js"</script>
你同样可以通过 NPM 安装:
1
npm install pjax
注意: 如果你通过 NPM 安装,你需要从下面两种方案中选择一种:
将一个脚本标签链接到
pjax.js
或pjax.min.js
,例如:1
<script src="./node_modules/pjax/pjax.js"></script>
使用像 Webpack 这样的 bundler。(如果没有 bundler,
index.js
就不能在浏览器中使用)。
或者你可以克隆这个仓库,并使用 npm 通过源代码构建程序。
1
2
3
4git clone https://github.com/MoOx/pjax.git
cd pjax
npm install
npm run build然后将
pjax.js
或pjax.min.js
引入到HTML中,例如:1
<script src="./pjax.min.js"></script>
Pjax的功能
在代码的封装下,它只是一个带有pushState()
调用的HTTP请求。
Pjax 使用 AJAX 加载页面,并使用pushState()
更新浏览器的 URL,在这个过程中不需要重新加载你的页面布局或任何资源(JS、CSS),从而加快页面的加载。
它适用于所有的 permalinks(永久链),可以更新页面的所有部分(包括HTML metas
、标题和导航栏)。
在不支持history.pushState()
的浏览器中,Pjax 不会修改任何内容。
除此之外,Pjax 还有以下优点:
- 不像 jQuery-Pjax 那样被限制在一个容器中
- 支持浏览器历史记录(后退和前进按钮)
- 支持键盘浏览
- 对外部页面自动返回到标准导航(感谢Captain Obvious的帮助)
- 对于没有适当 DOM 树的内部页面,自动返回到标准导航
- 可以非常容易地添加 CSS 动画
- 只有 6kb 左右(经压缩和 gzipped)
Pjax的工作原理
- 它监听你想要监听的所有链接的点击(默认是所有的)
- 当一个站内链接被点击时,Pjax 通过 AJAX 获取该页面的 HTML
- Pjax 渲染页面的 DOM 树(不加载任何资源 - 图片、CSS、JS 等)
- 它检查元素是否可以被替换
- 如果页面不符合要求,就会使用标准导航
- 如果页面符合要求,Pjax 会替换需要替换的元素
- 然后,它使用
pushState()
更新浏览器的当前URL
概述
Pjax 是全自动的,你不需要改变你现有的 HTML,你只需要指定当你的网站进行跳转时,你希望页面上的哪些元素被替换。
参考下面的例子:
1 |
|
我们希望Pjax能够拦截URL/about
,并将.the-content
替换为请求的结果内容。简单说就是在用户点击a
标签时阻止浏览器跳转,而是通过 Pjax 来切换页面。
因为不同页面之间的<nav>
、meta
及<aside>
内容可能不同,所以如果我们能够在切换页面时更新它们那就更好了。
总的来说,我们想在不重新加载样式或脚本的情况下,更新页面标题、meta
、顶栏、内容区和侧边栏。
Pjax 当然支持这些操作,我们可以通过告诉 Pjax 监听所有a
标签的点击(这是默认的),并使用上面定义的 CSS 选择器(不要忘记最小的 meta)来轻松做到这一点:
1 |
|
现在,当有人使用兼容 Pjax 的浏览器浏览网页并点击站内链接时,Pjax 将接管页面的切换任务,所有需要更新的内容将被替换为在 HTML 中找到的特定内容片段。
神奇的是!是真的! 你不需要在服务器端做任何事情!
与jQuery-pjax的不同点
- 没有 jQuery 的依赖性
- 不局限于一个容器
- 不需要在服务端进行适配
- 适用于 CommonJS 环境(Webpack / Browserify)、AMD(RequireJS),甚至全局
- 允许用 CSS 动画进行页面转换
- 可以很容易地进行调整,因为每个方法都是公开的(因此,是可以重写的)
兼容性
Pjax 只适用于支持history.pushState() API
的浏览器,当浏览器不支持该 API 时,Pjax 会进入回退模式(就是什么都不做)。
要想知道你的浏览器是否真的支持 Pjax,可以使用Pjax.isSupported()
。
用法
new Pjax()
让我们多说一说最基本的入门方法。
在实例化 Pjax 时,你可以把设置项作为一个对象传入构造函数:
1 |
|
这将在所有a
标签上上启用 Pjax,并使用CSS选择器指定要替换的部分:title
、.the-header
、.the-content
、.the-sidebar
。
在某些情况下,你可能想只针对某一些特定的元素来应用 Pjax。在这种情况下,你有两种方法:
- 使用一个自定义的CSS选择器(如
a.js-Pjax
或.js-Pjax a
等等) - 覆盖
Pjax.prototype.getElements
注意:如果这样做,请确保返回一个NodeList。1
2// 法一
const pjax = new Pjax({ elements: "a.js-Pjax" })1
2
3
4
5
6// 法二
Pjax.prototype.getElements = function() {
return document.getElementsByClassName(".js-Pjax")
}
const pjax = new Pjax()
loadUrl(href, [options])
通过这种方法,你可以手动触发一个 URL 的跳转。(相当于没有pjax
的情况下使用location.href = '...'
。)
1 |
|
handleResponse(responseText, request, href, options)
这个方法接收原始响应,处理 URL,然后调用pjax.loadContent()
将其实际加载到 DOM。
它接受以下参数:
responseText (string)
- 拉取到的文本,这等同于request.responseText
request (XMLHttpRequest)
- XHR 对象href (string)
- 传递给loadUrl()
的 URLoptions (object)
- 包含该请求的选项的对象,其结构基本上与常规的 options 对象一致,并有一些额外的内部属性
如果您想在将数据加载到 DOM 之前处理数据,或者不想将其加载到 DOM 中,则可以覆盖此选项。
例如,如果要检查 non-HTML 响应,可以执行以下操作:
1 |
|
refresh([el])
使用此方法将 Pjax 绑定到 DOM 元素的子元素上,这些元素在 Pjax 被初始化时并不存在。例如,由其他库或脚本动态插入的内容。如果调用时没有传入参数,Pjax 将再次解析整个文档以寻找新插入的元素。
1 |
|
reload()
强制刷新页面,等价于location.reload()
。
虽然官方文档中写了“强制”一词,但是通过我个人尝试,该函数被调用时 Pjax 仍然不会重新执行 js 以及重新加载 css。
1 |
|
Options(设置项)
elements
类型:string, 默认:"a[href], from[action]"
这个 CSS 选择器用于帮助 Pjax 寻找要应用的链接,如果需要多个特定的选择器,请用逗号将它们分开。
1 |
|
1 |
|
selectors
类型:Array, 默认:"title", ".js-Pjax"
这个 CSS 选择器用于告知 Pjax 哪些元素在链接跳转时需要被更新。
1 |
|
如果一个查询返回多个项目,它将只保留索引。
示例:
1 |
|
这个例子是正确的,应该可以“按预期”工作。
注意: 如果当前页面和新页面的 DOM 元素数量不一样,Pjax 将回退到普通的页面加载。
switches
类型:object, 默认:{}
这是一个包含回调的对象,可用于用新旧元素的切换。
对象的key
应该是定义的选择器之一(来自selectors
选项)。
示例:
1 |
|
回调函数中的this
指向pjax
对象(例如:this.onSwitch()
)。
内置的回调
Pjax.switches.outerHTML
- 默认行为,使用outerHTML
替换元素Pjax.switches.innerHTML
- 使用innerHTML
替换元素并复制className
Pjax.switches.replaceNode
- 使用replaceChild
来替换元素Pjax.s switches.sideBySide
: - 智能替换,当你想使用CSS动画时,允许你将两个元素放在同一个父元素中。当所有的子元素的动画结束(animationend
事件被触发)时,旧的元素就会被移除
编写一个回调
你的回调函数可以做任何你想做的事情,但你必须执行下面两个行为:
- 以某种方式将旧元素的内容替换为新元素的内容
- 调用
this.onSwitch()
来触发附加的回调
下面是默认的行为,我们拿他做一个例子:
1 |
|
switchesOptions
类型:object, 默认:{}
这些选项可在内容替换期间使用(通过 switches)。目前,只有Pjax.switches.sideBySide
使用它。当你使用类似Animate.css的东西时,不管有没有WOW.js,这都是非常方便的。
1 |
|
注意,remove
包含Animated——reverse
,这是一个简单的方法,不必有重复的转换(slideIn + reverse => slideOut)。
下面是一个与上述配置配合良好的 css:
1 |
|
为了说明这个 CSS 的效果,我们提供一个 HTML 片段:
1 |
|
history
类型:boolean, 默认:true
启用pushState()
,禁用它将阻止 Pjax 更新浏览器历史记录。虽然可以这么做,但很少有需要这样的场景。
在内部,这个选项是在popstate
事件触发时给 Pjax 使用的(为了不再次调用pushState()
)。
analytics
类型:Function|Boolean, 默认:一个推送_gap``_trackPageview
或者发送ga``pageiew
的函数
允许您添加分析行为的函数。默认情况下,它会尝试使用 Google Analytics 跟踪页面视图(如果页面上存在)。每次切换页面时都会调用它,即使对于历史导航也是如此。
设置为false
可禁用此行为。
scrollTo
类型:Integer|[Integer, Integer]|False, 默认:0
当设置为一个整数时,这是切换页面时要滚动到的值(从页面的顶部开始,单位:px)。
当设置为 2 个整数的数组([x, y])时,这是水平和垂直方向的滚动值。
设置为false
则禁用滚动,这将意味着页面将保持在加载新元素之前的那个位置。
scrollRestoration
类型:Boolean, 默认:true
当设置为true
时,Pjax 将尝试在向后或向前导航时恢复滚动位置。
cacheButs
类型:Boolean, 默认:true
当设置为true
时,Pjax 会在请求的 URL 上附加一个时间戳,以跳过浏览器的缓存。
debug
类型:Boolean, 默认:false
启用调试模式,这对页面调试很有用。
currentUrlFullReload
类型:Boolean, 默认:false
当设置为true
时,点击一个指向当前 URL 的链接将触发全页面重载。
当设置为false
时,点击这样的链接将导致 Pjax 加载当前页面,而不进行全页面重载。如果你想添加一些自定义行为,可以给链接添加一个点击监听器并调用preventDefault()
。这将阻止 Pjax 接收该事件。
注意: 事件的注册必须在 Pjax 被实例化之前完成,否则 Pjax 的事件处理程序将被首先调用,而preventDefault()
还没有被调用。
下面是一些示例代码:
1 |
|
(注意,如果cacheBust
被设置为true
,检查 href 是否与当前页面的 URL 相同的代码将不起作用,这是因为附加了一个时间戳来强行破坏缓存。)
timeout
类型:Integer, 默认:0
XHR 请求的超时(毫秒),设置为0
表示禁用超时。
事件
无论是如何调用 Pjax,其都会触发一些事件。
所有的事件都是从document
中触发的,而不是被点击的链接。
pjax:send
- 在 Pjax 请求开始后触发pjax:complete
- 在 Pjax 请求结束后触发pjax:success
- 在 Pjax 请求成功后触发pjax:error
- 在 Pjax 请求失败后触发,请求对象将作为event.options.request
传递
如果要实现加载指示,那么send
和complete
是一对很好的事件。(示例:topbar)
1 |
|
HTTP Headers
Pjax 在发出和接收 HTTP 请求时,会使用一些自定义的标头。如果这些请求被送到你的服务器,你可以使用这些标头来获得一些关于响应的 mate 信息。
Request Headers
Pjax 在每个请求中都会发送以下头信息。
X-Requested-With: "XMLHttpRequest"
X-PJAX: "true"
X-PJAX-Selectors
: 一个选择器的序列化 JSON 数组,取自options.selectors
。你可以用它来只发送 Pjax 需要更新的元素,而不是发送整个页面。请注意,你需要在服务器上对其进行反序列化(例如使用JSON.parse()
)。
Response Headers
Pjax 会在响应头中查询以下信息:
X-PJAX-URL
或X-XHR-Redirected-To
Pjax 首先检查 XHR 对象上的responseURL
属性,看请求是否被服务器重定向了。虽然大多数浏览器支持这个,但不是所有的。为了确保 Pjax 能够分辨出请求是否被重定向,你可以在响应中包含一个这样的 header,并设置为最终的 URL。
DOM就绪状态
大多数时候,你会有与当前 DOM 相关的代码,需要在 DOM 准备好后再执行。
由于 Pjax 不会在你每次加载页面时自动重新执行你之前的代码,你需要添加代码来重新触发 DOM 准备好的代码,下面是一个简单的例子:
1 |
|
注意:不要在whenDOMReady
函数中创建pjax
实例。
如果你只想更新一个特定的部分(这是一个好主意),你可以在一个函数中添加 DOM 相关的代码,当pjax:success
事件被触发时,重新执行这个函数。
1 |
|
FAQ
Q: 启用 Pjax 后 Disqus 不能正常工作,我应该如何解决这个问题?
A: 你只需要做下面这几件事情:
将你的 Disqus 片段包裹到一个 DOM 元素中,你将把它添加到selectors
中(或者直接用class="js-Pjax"
的元素包裹它),并确保在每个页面上至少有一个空的包裹器(以避免页面之间 DOM 的差异)。
编辑你的 Disqus 代码,就像下面这样:
Pjax 执行前的片段。
1 |
|
Pjax 执行后的片段。
1 |
|
注意:Pjax 只运行你正在更新的容器的内联 <script> 块。
示例
克隆这个资源库并运行npm run example
,这将在你的浏览器中打开示例应用程序。
补充
- 当你手动调用
pushState
或replaceState
时不要通过state
来存储状态,也不要覆盖原有的state
,因为 Pjax 通过这些state
保证页面的正常加载。如果你通过state
存储数据,Pjax 随时可能覆盖它,如果你覆盖了原有的state
,可能会导致使用浏览器前进或回退功能时出现 404 错误。 - 部分浏览器虽然支持
pushState
但没有完美支持(支持了,但又没有支持),现象是 Pjax 任何功能都可以正常工作,但是浏览器的url
不会变化(历史记录正常)。比如说夸克浏览器就是这个样子。