引入PJAX
- 从 MoOx/pjax(不带JQuery)这里引入
- 我们需要刷新的是
左侧边栏,右侧边栏和中间的内容
- 从源代码可以发现这些元素都被一个
<div class="main-container">...</div>包裹着 - 根据官方文档,在
layouts/partials/footer/custom.html加入以下代码来引入PJAX:
1<!-- 【custom.html】 -->
2<script src="https://cdn.jsdelivr.net/npm/pjax/pjax.min.js"></script>
3<script>
4 var pjax = new Pjax({
5 selectors: [
6 ".main-container"
7 ]
8 })
9</script>
- 我这里先创建
layouts/partials/footer/pjax.html,再在custom.html里引用
1<!-- pjax.html -->
2<script src="https://cdn.jsdelivr.net/npm/pjax/pjax.min.js"></script>
3<script>
4 var pjax = new Pjax({
5 selectors: [
6 ".main-container",
7 ]
8 })
9</script>
10
11<!-- custom.html -->
12{{partial "footer/pjax.html"}}
文章样式修复
- 问题描述:
- 引入pjax后,重启
hugo server -D,发现文章样式丢失,手动点击刷新可以恢复
- 引入pjax后,重启
- 产生原因:
<body>标签中的class名缺失article-page,导致文章样式丢失
- 解决思路:
- 通过官方提供了数据预处理方法,来预处理数据,获取到新页面的className,然后我们手动将这className设置到
<body>上
- 通过官方提供了数据预处理方法,来预处理数据,获取到新页面的className,然后我们手动将这className设置到
- 具体步骤:
- 修改
layouts/partials/footer/pjax.html,(如果是在custom.html加了pjax就在那里改)引入以下代码:
1<script>
2 pjax._handleResponse = pjax.handleResponse;
3 pjax.handleResponse = function(responseText, request, href, options) {
4 if (request.responseText.match("<html")) {
5 if (responseText) {
6 // 将新页面的html字符串解析成DOM对象
7 let newDom = new DOMParser().parseFromString(responseText, 'text/html');
8 // 获取新页面中body的className,并设置回当前页面
9 let bodyClass = newDom.body.className;
10 document.body.setAttribute("class", bodyClass)
11 }
12 // 放行,交给pjax自己处理
13 pjax._handleResponse(responseText, request, href, options);
14 } else {
15 // handle non-HTML response here
16 }
17 }
18</script>
主题切换修复
- 问题描述:
- 切换页面后,左下角切换主题颜色按钮不生效
- 产生原因:
- 在Stack主题源码
assets/ts/colorScheme.ts中,脚本初始化时,会给元素绑定一个点击事件。但因为页面切换了,替换了该元素,但该元素没有重新绑定点击事件,导致点击主题切换失效
- 在Stack主题源码
-
解决思路:
- 在PJAX切换完页面后,重新执行一遍
colorScheme.ts的初始化,使元素重新绑定点击事件 - 而
colorScheme.ts被main.ts引用,在main.ts中执行了初始化,并且main.ts生成了全局变量 Stack
- 所以在PJAX执行完后,使用全局变量 Stack ,执行里面的初始化方法,重新执行一遍脚本,来绑定点击事件
- PJAX官方文档也提供了PJAX执行完后的事件
pjax:complete,监听这个事件,Stack 执行初始化就好
- 在PJAX切换完页面后,重新执行一遍
-
具体步骤:
- 修改
layouts/partials/footer/pjax.html,引入以下代码
- 修改
1<script>
2 document.addEventListener('pjax:complete', () => {
3 // Stack脚本初始化
4 window.Stack.init();
5 })
6</script>
文章搜索修复
- 问题描述:
- 使用文章搜索功能时,输入关键词,无任何搜索记录
- 产生原因:
- 查看
assets/ts/search.tsx文件,情况和上面的colorScheme.ts类似,存在绑定事件
- 查看
- 解决思路:
- 把 search.tsx 初始化内容封装为一个函数,并把函数 export 出来
- 由 main.ts 引入这个函数,并放到 Stack.init() 的方法中,利用此方法来重新初始化搜索脚本
- 具体步骤:
- 修改
assets/ts/search.tsx代码,封装方法并export
1/**
2 * 把window.addEventListener('load' ...这部分代码注释掉
3 * 初始化工作交给Stack.init()处理了,不需要这个了
4 */
5...
6function searchInit() {
7 let search = document.querySelector('.search-result');
8 if (search) {
9 const searchForm = document.querySelector('.search-form') as HTMLFormElement,
10 searchInput = searchForm.querySelector('input') as HTMLInputElement,
11 searchResultList = document.querySelector('.search-result--list') as HTMLDivElement,
12 searchResultTitle = document.querySelector('.search-result--title') as HTMLHeadingElement;
13
14 new Search({
15 form: searchForm,
16 input: searchInput,
17 list: searchResultList,
18 resultTitle: searchResultTitle,
19 resultTitleTemplate: window.searchResultTitleTemplate
20 });
21 }
22}
23
24export {
25 searchInit
26}
- 修改
assets/ts/main.ts,引入搜索初始化方法并调用
1...
2import { searchInit } from "ts/search";
3let Stack = {
4 init: () => {
5 ...
6 // 调用search脚本初始化方法
7 searchInit();
8 }
9}
- 从Stack模板文件复制
layouts\partials\footer\components\script.html,把"JSXFactory" "createElement"补充,改法参考layouts\page\search.html,
搜索内容跳转修复
-
问题描述:
- 能进行搜索,但搜索出来的内容并没有被PJAX识别到,导致PJAX没有拦截,进而导致页面刷新
-
产生原因:
- 阅读 search.tsx 源码可知,搜索内容的数据,是通过 React.render() 动态渲染回页面的,这些动态数据没有被PJAX识别到
- 解决思路:
- PJAX官方文档提供给了重新解析数据的方法,所以在 React.render() 之后,调用PJAX方法,重新解析页面即可
- 具体步骤:
- 修改
assets/ts/search.tsx,在动态渲染数据方法末尾让pjax重新解析文档
1private async doSearch(keywords: string[]) {
2 ...
3 /*
4 方法末尾,让pjax重新解析文档数据,识别动态渲染的数据
5 虽然当前文件没有pjax对象,但最后静态页面会生成一个整体的js文件
6 pjax对象那时就能识别到,就可成功调用
7 */
8 pjax.refresh(document);
9}
KaTeX修复
KaTex渲染失效
- 问题描述:
- 含有 数学公式(KaTeX) 的文章,里面的数学公式不能正常渲染出来
- 产生原因:
- 阅读源码
layouts/partials/article/components/math.html,KaTeX渲染公式需要执行renderMathInElement,而PJAX的无刷新技术无法触发DOMContentLoaded的监听
- 阅读源码
- 解决思路:
- 将renderMathInElement封装为函数,交由PJAX加载结束后执行
- 具体操作:
- 修改
layouts/partials/article/components/math.html,添加一个元素标签,便于判断文档是否使用了KaTeX
1...
2<div class="math-katex"></div>
layouts/partials/footer/pjax.html,引入以下代码
1<script>
2 async function renderKaTeX() {
3 // 判断当前页面是否有KateX
4 let katex = document.querySelector(".math-katex");
5 if (!katex) {
6 return;
7 }
8 // 等待函数加载成功后,再执行渲染方法
9 while (typeof renderMathInElement !== 'function') {
10 await delay(500);
11 }
12 // KaTeX渲染方法
13 renderMathInElement(document.body, {
14 delimiters: [
15 { left: "$$", right: "$$", display: true },
16 { left: "$", right: "$", display: false },
17 { left: "\\(", right: "\\)", display: false },
18 { left: "\\[", right: "\\]", display: true }
19 ],
20 ignoredClasses: ["gist"]
21 });
22 }
23
24 /**
25 * 同步延迟
26 */
27 function delay(time) {
28 return new Promise(resolve => {
29 setTimeout(resolve, time)
30 })
31 }
32
33 document.addEventListener('pjax:complete', () => {
34 renderKaTeX();
35 })
36</script>
引用块和公式块冲突
-
问题描述:
- 在引用块中又写了公式块,最终渲染出这样:
-
产生原因:
- Typora 内置的 Markdown 解析器 先合并 blockquote 行、再交给 MathJax,所以Typora显示正常
- 而 Hugo(Goldmark 解析器)逐行解析,每一行前面都带着
>,于是 Goldmark 把>转义成>塞进公式节点,KaTeX 收到的是:> \mu(z_t) = 0.8 \quad (...)
-
解决思路:
- 目前没有很好的解决方法,只能是避免混用
浏览量统计失效
- 问题描述:
- 先前使用的vercount在引入Pjax后失效,必须手动刷新才会生效
- 产生原因:
- 原始的Vercount脚本在页面第一次加载时执行,它会在页面中插入浏览量显示的元素。但是当使用Pjax加载x新页面时,脚本不会自动重新执行,因此新页面中的浏览量元素没有被插入。
- 解决思路:
- 动态重新加载脚本,移除旧脚本,清除之前的执行状态,
- 创建新脚本,等待 pjax:complete 完成后再添加到 DOM 中
- 具体步骤:
layouts/partials/footer/pjax.html,引入代码
1<script>
2 // 浏览量统计
3 function initVerCount() {
4 // 移除已存在的 Vercount 脚本
5 const existingScript = document.querySelector('script[src="https://cn.vercount.one/js"]');
6 if (existingScript) {
7 existingScript.remove();
8 }
9
10 // 创建新的 Vercount 脚本
11 const newScript = document.createElement('script');
12 newScript.src = 'https://cn.vercount.one/js';
13 newScript.defer = true;
14
15 // 添加到 head 中
16 document.head.appendChild(newScript);
17 }
18
19 document.addEventListener('pjax:complete', () => {
20 ...
21 initVerCount();
22 })
23</script>
网站标题更改
- 问题描述:
- 使用了PJAX后,点击左侧边栏和文章,网站标题都是你在
.yaml中设置的languages.zh-cn.title,当你手动刷新才会变成相关页面
- 使用了PJAX后,点击左侧边栏和文章,网站标题都是你在
- 产生原因:
- PJAX 通过 AJAX 加载新页面的内容(这里配置的 selectors: [".main-container"],只替换主容器),而不进行完整的页面重载(full page reload)。这意味着
title部分没有进行更新。
- PJAX 通过 AJAX 加载新页面的内容(这里配置的 selectors: [".main-container"],只替换主容器),而不进行完整的页面重载(full page reload)。这意味着
- 解决思路:
- 在
handleResponse函数中添加逻辑:从新页面的 HTML 响应中提取<title>标签,设置到document.title
- 在
- 具体步骤:
- 在
\layouts\partials\footer\pjax.html中修改文章格式的同时,添加title
1<script>
2 // pjax刷新区域
3 var pjax = new Pjax({
4 selectors: [
5 ".main-container",
6 ]
7 })
8
9 // 修正文章样式丢失
10 pjax._handleResponse = pjax.handleResponse;
11 pjax.handleResponse = function(responseText, request, href, options) {
12 if (request.responseText.match("<html")) {
13 // 将新页面的html字符串解析成DOM对象
14 let newDom = new DOMParser().parseFromString(responseText, 'text/html');
15
16 // 1. 更新 body class,获取新页面中body的className,并设置回当前页面
17 let bodyClass = newDom.body.className;
18 document.body.setAttribute("class", bodyClass)
19
20 // 2. 更新 <title>
21 let newTitle = newDom.querySelector('title');
22 if (newTitle && newTitle.textContent) {
23 document.title = newTitle.textContent.trim();
24 }
25
26 pjax._handleResponse(responseText, request, href, options);
27 } else {
28 // handle non-HTML response here
29 }
30 }
31...
32</script>
进度条加载
- 问题描述:
- 使用了PJAX后,原先采用的进度条失效,采用另外的方法
- 具体步骤:
- 前往【topbar】下载zip包,将解压后的 topbar.min.js 放到
assets\js\topbar.min.js - 通过监听PJAX两个事件 pjax:send 和 pjax:complete 实现伪进度条
layouts/partials/footer/pjax.html,引入代码
1{{ with resources.Get "js/topbar.min.js" }}
2 <!-- 引入本地JS脚本 -->
3 <script src={{ .Permalink }}></script>
4{{ end }}
5<script>
6 // 修改进度条颜色
7 topbar.config({
8 barColors: {
9 '0': 'rgba(255, 255, 255, 1)', // 进度0%白色
10 '1.0': 'rgba(0, 149, 234, 1)' // 进度100%蓝色
11 }
12 })
13
14 document.addEventListener('pjax:send', () => {
15 // 显示顶部进度条
16 topbar.show();
17 })
18
19 document.addEventListener('pjax:complete', () => {
20 ....
21 // 隐藏顶部进度条
22 topbar.hide();
23 })
24</script>