【Hugo】PJAX实现无刷新加载页面

|
|
|

pjax = pushState + ajax,无刷新ajax加载页面


前言:


1 引入PJAX

  • (1)PJAX主要分为两个版本,带JQuery不带JQuery的,下面我演示的是不带JQuery版本的

  • (2)分析页面元素,看哪些是需要我们重新加载的,可以发现是左侧边栏中间内容右侧边栏

  • (3)查看页面源代码,可以发现这些元素都被一个<div class="main-container">...</div>包裹着,所以我们将这元素定为要刷新的对象

  • (4)根据官方文档,在layouts/partials/footer/custom.html加入以下代码来引入PJAX
1
2
3
4
5
6
7
8
9
<!-- 【custom.html】 -->
<script src="https://cdn.jsdelivr.net/npm/pjax/pjax.min.js"></script>
<script>
	var pjax = new Pjax({
	  selectors: [
	    ".main-container"
	  ]
	})
</script>

这样算是已经基本引入了PJAX,但也带来了不少问题,我们一步一步进行修复


2 问题修复

2.1 文章样式修复

  • 问题描述:
    • 随便点进其中一篇文章,可以发现文章内容的样式丢失

  • 产生原因:
    • <body>标签中的class名缺失article-page,导致文章样式丢失

  • 解决思路:
    • 通过官方提供了数据预处理方法,来预处理数据,获取到新页面的className,然后我们手动将这className设置到<body>

  • 具体步骤:
  • (1)修改layouts/partials/footer/custom.html,引入以下代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<script>
    pjax._handleResponse = pjax.handleResponse;
    pjax.handleResponse = function(responseText, request, href, options) {
        if (request.responseText.match("<html")) {
            if (responseText) {
                // 将新页面的html字符串解析成DOM对象
                let newDom = new DOMParser().parseFromString(responseText, 'text/html');
                // 获取新页面中body的className,并设置回当前页面
                let bodyClass = newDom.body.className;
                document.body.setAttribute("class", bodyClass)
            }
            // 放行,交给pjax自己处理
            pjax._handleResponse(responseText, request, href, options);
        } else {
            // handle non-HTML response here
        }
    }
</script>

2.2 主题切换修复

  • 问题描述:
    • 当我们切换页面后,点击左下角切换主题颜色的按钮,会发现没有效果,主题颜色切换失效了

  • 产生原因:
    • 阅读Stack主题源码assets/ts/colorScheme.ts发现,在脚本初始化时,会给元素绑定一个点击事件。但因为页面切换了,替换了该元素,但该元素没有重新绑定点击事件,导致点击主题切换失效

  • 解决思路:
    • 在PJAX切换完页面后,重新执行一遍colorScheme.ts的初始化,使元素重新绑定点击事件。
    • 阅读源码,发现colorScheme.tsmain.ts引用,在main.ts中执行了初始化,并且main.ts生成了个全局变量 Stack
    • 所以在PJAX执行完后,使用全局变量 Stack ,执行里面的初始化方法,重新执行一遍脚本,来绑定点击事件
    • 阅读PJAX文档,发现官方也提供了PJAX执行完后的事件,我们执行监听这个事件,Stack 执行初始化就好

  • 具体步骤:
  • (1)修改layouts/partials/footer/custom.html,引入以下代码
1
2
3
4
5
6
<script>
    document.addEventListener('pjax:complete', () => {
        // Stack脚本初始化
        window.Stack.init();
    })
</script>

2.3 文章搜索修复

  • 问题描述:
    • 使用文章搜索功能时,输入关键词,无任何搜索记录,搜索功能失效

  • 产生原因:
    • 查看assets/ts/search.tsx文件,发现情况和上面的colorScheme.ts类似,存在绑定事件

  • 解决思路:
    • search.tsx 初始化内容,封装为一个函数,并把函数 export 出来
    • main.ts 引入这个函数,并放到 Stack.init() 的方法中,利用此方法来重新初始化搜索脚本

  • 具体步骤:
  • (1)修改assets/ts/search.tsx代码,封装方法并export
 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
/**
 * 记得把window.addEventListener('load' ...这部分代码注释掉
 * 初始化工作交给Stack.init()处理了,不需要这个了
 */  
...
function searchInit() {
    let search = document.querySelector('.search-result');
    if (search) {
        const searchForm = document.querySelector('.search-form') as HTMLFormElement,
            searchInput = searchForm.querySelector('input') as HTMLInputElement,
            searchResultList = document.querySelector('.search-result--list') as HTMLDivElement,
            searchResultTitle = document.querySelector('.search-result--title') as HTMLHeadingElement;

        new Search({
            form: searchForm,
            input: searchInput,
            list: searchResultList,
            resultTitle: searchResultTitle,
            resultTitleTemplate: window.searchResultTitleTemplate
        });
    }
}

export {
    searchInit
}
  • (2)修改assets/ts/main.ts,引入搜索初始化方法并调用
1
2
3
4
5
6
7
8
9
...
import { searchInit } from "ts/search";
let Stack = {
    init: () => {
        ...
        // 调用search脚本初始化方法
        searchInit();
    }
}
  • (3)tsx 类型的文件引入方式有点特殊,需要我们修改以下 main.ts 的引入方式,修改layouts\partials\footer\components\script.html,改法参考layouts\page\search.html,把"JSXFactory" "createElement"补充上就好


2.4 搜索内容跳转修复

  • 问题描述:
    • 虽然文章搜索已经修复了,但搜索出来的内容并没有被PJAX识别到,导致PJAX没有拦截,进而导致页面刷新了

  • 产生原因:
    • 阅读 search.tsx 源码可知,搜索内容的数据,是通过 React.render() ,动态渲染回页面的,这些动态数据没有被PJAX识别到。

  • 解决思路:
    • 阅读PJAX文档,官方提供给了我们重新解析数据的方法,所以在 React.render() 之后,调用PJAX方法,重新解析页面即可

  • 具体步骤:
  • (1)修改assets/ts/search.tsx,在动态渲染数据方法末尾让pjax重新解析文档
1
2
3
4
5
6
7
8
9
private async doSearch(keywords: string[]) {
    ...
    /* 
    方法末尾,让pjax重新解析文档数据,识别动态渲染的数据
    虽然当前文件没有pjax对象,但最后静态页面会生成一个整体的js文件
    pjax对象那时就能识别到,就可成功调用
    */
    pjax.refresh(document);
}

2.5 文章评论修复

  • 问题描述:
    • 如果博客有开启评论功能的话,会发现评论的脚本不生效了

  • 产生原因:
    • pjax引入自动执行的脚本,不支持自行执行

  • 解决思路:
    • 由于每个人使用的评论种类各不相同,所以这边也是简单说一下。PJAX官方文档也有说如何处理:
    • 用一个<div class=js-Pjax></div>来包裹一个<script></script>
    • <script>标签中,通过document.createElement('script')形式,创建对应的评论脚本内容
    • pjax对象通过 .js-Pjax 被识别到此内容,进行脚本执行

  • 具体步骤:
  • (1)修改layouts/partials/footer/custom.html,把实际的评论参数填写进行下面模板
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
<script>
    var pjax = new Pjax({
        selectors: [
            ...
            ".js-Pjax"
        ]
    })
</script>
<div class="js-Pjax">
    <script>
        // TODO: 判断当前是否是文档,且是否开启评论功能
        (function() {
            let script = document.createElement('script');
            // 将对应评论的脚本内容填进去
             script.setAttribute('key', 'value');
            ...
            // 寻找合适的元素,添加脚本进去
            document.querySelector('xxx').appendchild(script)
        })(document)
    </script>
<div>

2.6 KaTeX修复

  • 问题描述:
    • 点进含有 数学公式(KaTeX) 的文章,里面的数学公式不能正常渲染出来

$\varphi = \dfrac{1+\sqrt5}{2}= 1.6180339887…$

  • 产生原因:
    • 阅读源码layouts/partials/article/components/math.html,KaTeX渲染公式需要执行renderMathInElement,而PJAX的无刷新技术无法触发DOMContentLoaded的监听

  • 解决思路:
    • 将KaTeX渲染方法renderMathInElement封装为函数,交由PJAX加载结束后执行

  • 具体操作:
  • (1)修改layouts/partials/article/components/math.html,添加一个元素标签,便于判断文档是否使用了KaTeX
1
2
...
<div class="math-katex"></div>
  • (2)layouts/partials/footer/custom.html,引入以下代码
 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
<script>
    async function renderKaTeX() {
        // 判断当前页面是否有KateX
        let katex = document.querySelector(".math-katex");
        if (!katex) {
            return;
        }
        // 等待函数加载成功后,再执行渲染方法
        while (typeof renderMathInElement !== 'function') {
            await delay(500);
        }        
        // KaTeX渲染方法
        renderMathInElement(document.body, {
            delimiters: [
                { left: "$$", right: "$$", display: true },
                { left: "$", right: "$", display: false },
                { left: "\\(", right: "\\)", display: false },
                { left: "\\[", right: "\\]", display: true }
            ],
            ignoredClasses: ["gist"]
        });
    }
    
    /**
     * 同步延迟
     */
    function delay(time) {
        return new Promise(resolve => {
            setTimeout(resolve, time)
        })
    }

    document.addEventListener('pjax:complete', () => {
        renderKaTeX();
    })
</script>

3 引入进度条

  • (1)由于使用了PJAX后,无法得知页面的加载情况是否完成,所以引入一个伪进度条,来显示页面内容进度

  • (2)前往【topbar】,点击下载zip包,将解压后的 topbar.min.js 放到assets\js\topbar.min.js

  • (3)通过监听PJAX两个事件 pjax:sendpjax:complete 实现伪进度条

 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
<!--custom.html-->
...
{{ with resources.Get "js/topbar.min.js" }}
    <!-- 引入本地JS脚本 -->
    <script src={{ .Permalink }}></script>
{{ end }}
<script>
    // 修改进度条颜色
    topbar.config({
        barColors: {
            '0': 'rgba(255,  255, 255, 1)', // 进度0%白色
            '1.0': 'rgba(0, 149, 234,  1)' // 进度100%蓝色
        }
    })
	
    document.addEventListener('pjax:send', () => {
        // 显示顶部进度条
        topbar.show();
    })
	
    document.addEventListener('pjax:complete', () => {
        ....
        // 隐藏顶部进度条
        topbar.hide();
    })
</script>

这样伪进度条就成功引入了,能大概知道页面的加载情况了


根据CC BY-NC-SA 4.0协议授权
使用 Hugo 构建
主题 StackJimmy 设计