博客美化篇(五):为博客添加 PJAX

什么是 PJAX?

PJAX(PushState + AJAX)是一种结合了HTML5的pushState API和AJAX技术的网页加载方法。它通过在后台异步加载页面内容,然后只更新页面中需要改变的部分,同时通过pushState更新浏览器地址栏,从而实现了更快的页面切换体验。

传统的网页导航会导致整个页面重新加载,包括CSS、JavaScript和重复的页面结构(如页眉、页脚)。而PJAX只加载变化的内容部分,保持其他部分不变,大大减少了数据传输量和渲染时间。

为什么要在博客中添加 PJAX?

主要为了保持音乐播放器播放状态不被中断

由于博主也是第一次接触Pjax,在使用Pjax时,发现页面的许多脚本无法正常执行,具体问题包括:黑暗模式切换、图片懒加载、一言、搜索、AI 摘要、标签云、时钟、DPLAYER、公式和流程图等。由于这些问题,一度想放弃 Pjax 方案。不过,经过进一步的探索和尝试,发现通过调整脚本加载顺序、绑定事件监听等方式,可以逐步解决这些问题,最终成功让 Pjax 与这些功能兼容。

基本实现步骤

  1. 引入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');
  1. 编写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')
}
})
  • 如果请求返回 404 错误,加载 404 页面
请求成功时 (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,初始化图表并清除定时器

博客美化篇(五):为博客添加 PJAX
https://blog.cngo.xyz/posts/d023.html
作者
cngo
发布于
2024年7月30日
许可协议