什么是 PJAX?
PJAX(PushState + AJAX)是一种结合了HTML5的pushState API和AJAX技术的网页加载方法。它通过在后台异步加载页面内容,然后只更新页面中需要改变的部分,同时通过pushState更新浏览器地址栏,从而实现了更快的页面切换体验。
传统的网页导航会导致整个页面重新加载,包括CSS、JavaScript和重复的页面结构(如页眉、页脚)。而PJAX只加载变化的内容部分,保持其他部分不变,大大减少了数据传输量和渲染时间。
为什么要在博客中添加 PJAX?
主要为了保持音乐播放器播放状态不被中断
由于博主也是第一次接触Pjax,在使用Pjax时,发现页面的许多脚本无法正常执行,具体问题包括:黑暗模式切换、图片懒加载、一言、搜索、AI 摘要、标签云、时钟、DPLAYER、公式和流程图等。由于这些问题,一度想放弃 Pjax 方案。不过,经过进一步的探索和尝试,发现通过调整脚本加载顺序、绑定事件监听等方式,可以逐步解决这些问题,最终成功让 Pjax 与这些功能兼容。
基本实现步骤
引入PJAX库:
参考安知鱼主题的实现方式,引入Pjax库
1 2 3 4 5 6
| hexo.extend.injector.register('body_end', ` <script src="https://cdn.cbd.int/pjax@0.2.8/pjax.min.js"></script> <script src="/assets/js/pjax.js"></script> `, 'default');
|
编写Pjax处理逻辑:
完整代码(/assets/js/pjax.js):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| (function() { let enable = localStorage.getItem("enable-pjax"); if(eval(enable) === false) return; Fluid.utils.createScript('/assets/js/DPlayer.min.js') var box = document.getElementById('loading'); var pjaxSelectors = ["head > title", "#web_bg", ".header-inner", "#container", ".pjax-reload"]
var pjax = new Pjax({ elements: 'a:not([target="_blank"])', selectors: pjaxSelectors, cacheBust: false, analytics: false, scrollRestoration: false })
document.addEventListener('pjax:send', function () { box.style.display = 'flex' })
document.addEventListener('pjax:complete', function () { Fluid.events.registerScrollTopArrowEvent(); Fluid.events.registerImageLoadedEvent(); if($(".scroll-down-bar").length > 0){ Fluid.events.registerScrollDownArrowEvent(); } box.style.display = 'none'; document.querySelectorAll('script[data-pjax]').forEach(item => { const newScript = document.createElement('script') const content = item.text || item.textContent || item.innerHTML || "" Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value)) newScript.appendChild(document.createTextNode(content)) item.parentNode.replaceChild(newScript, item) }) })
document.addEventListener('pjax:error', e => { if (e.request.status === 404) { pjax.loadUrl('/404.html') } })
document.addEventListener('pjax:success', e => { if($(".mermaid").length > 0){ let i = setInterval(function(){ if("mermaid" in window){ mermaid.init() clearInterval(i) } }, 1000) } }) })();
|
1. 初始化检查
1 2
| let enable = localStorage.getItem("enable-pjax"); if(eval(enable) === false) return;
|
- 从本地存储中读取
enable-pjax
设置 - 如果该值为
false
,则直接退出,不启用 PJAX - 使用
eval()
将存储的字符串转换为布尔值(注意:实际项目中应避免直接使用 eval
,可以用 JSON.parse
替代)
2. 加载依赖
1
| Fluid.utils.createScript('/assets/js/DPlayer.min.js')
|
- 动态加载 DPlayer 脚本(一个视频播放器库)
- 处理包含视频内容的页面
3. PJAX 配置
1 2 3 4 5 6 7 8 9 10
| var box = document.getElementById('loading'); var pjaxSelectors = ["head > title", "#web_bg", ".header-inner", "#container", ".pjax-reload"]
var pjax = new Pjax({ elements: 'a:not([target="_blank"])', selectors: pjaxSelectors, cacheBust: false, analytics: false, scrollRestoration: false })
|
loading
元素是页面加载动画容器pjaxSelectors
定义了哪些部分的内容会被 PJAX 替换:- 页面标题 (
head > title
) - 背景元素 (
#web_bg
) - 头部内容 (
.header-inner
) - 主容器 (
#container
) - 需要重新加载的 PJAX 元素 (
.pjax-reload
)
- PJAX 初始化配置:
elements
: 哪些链接触发 PJAX(排除 target=”_blank” 的链接)selectors
: 要替换的内容选择器cacheBust
: 不添加缓存破坏参数analytics
: 不使用默认的分析跟踪scrollRestoration
: 禁用浏览器自动滚动恢复
4. PJAX 事件处理
发送请求时 (pjax:send
)
1 2 3
| document.addEventListener('pjax:send', function () { box.style.display = 'flex' })
|
请求完成时 (pjax:complete
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| document.addEventListener('pjax:complete', function () { Fluid.events.registerScrollTopArrowEvent(); Fluid.events.registerImageLoadedEvent(); if($(".scroll-down-bar").length > 0){ Fluid.events.registerScrollDownArrowEvent(); } box.style.display = 'none'; document.querySelectorAll('script[data-pjax]').forEach(item => { const newScript = document.createElement('script') const content = item.text || item.textContent || item.innerHTML || "" Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value)) newScript.appendChild(document.createTextNode(content)) item.parentNode.replaceChild(newScript, item) }) })
|
- 重新注册滚动到顶部按钮事件
- 重新注册图片加载事件
- 如果有向下滚动按钮,注册相应事件
- 隐藏加载动画
- 处理带有
data-pjax
属性的脚本:- 创建新脚本元素
- 复制原脚本的内容和所有属性
- 替换原脚本(确保脚本在新内容中执行)
发生错误时 (pjax:error
)
1 2 3 4 5
| document.addEventListener('pjax:error', e => { if (e.request.status === 404) { pjax.loadUrl('/404.html') } })
|
请求成功时 (pjax:success
)
1 2 3 4 5 6 7 8 9 10
| document.addEventListener('pjax:success', e => { if($(".mermaid").length > 0){ let i = setInterval(function(){ if("mermaid" in window){ mermaid.init() clearInterval(i) } }, 1000) } })
|
- 如果页面包含 Mermaid 图表(
.mermaid
元素):- 每隔 1 秒检查一次 mermaid 是否已加载
- 一旦检测到 mermaid,初始化图表并清除定时器