[Hugo] 美化过程中遇到的问题

引入PJAX

  • MoOx/pjax(不带JQuery)这里引入
  • 我们需要刷新的是左侧边栏右侧边栏中间的内容

image-20251025094633778

  • 从源代码可以发现这些元素都被一个<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,发现文章样式丢失,手动点击刷新可以恢复

image-20251025095926811

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

image-20251025095952597

image-20251025100303630

  • 解决思路
    • 通过官方提供了数据预处理方法,来预处理数据,获取到新页面的className,然后我们手动将这className设置到<body>
  • 具体步骤
  • 修改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>

主题切换修复

  • 问题描述
    • 切换页面后,左下角切换主题颜色按钮不生效

image-20251025101847388

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

image-20251025102047179

  • 解决思路

    • 在PJAX切换完页面后,重新执行一遍colorScheme.ts的初始化,使元素重新绑定点击事件
    • colorScheme.tsmain.ts引用,在main.ts中执行了初始化,并且main.ts生成了全局变量 Stack

    image-20251025103737944

    • 所以在PJAX执行完后,使用全局变量 Stack ,执行里面的初始化方法,重新执行一遍脚本,来绑定点击事件
    • PJAX官方文档也提供了PJAX执行完后的事件pjax:complete,监听这个事件,Stack 执行初始化就好
  • 具体步骤

    • 修改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类似,存在绑定事件

image-20251025105258492

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

image-20251025105800595

  • 具体步骤
  • 修改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识别到

image-20251025113007805

  • 解决思路:
    • 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的监听

image-20251025113619827

  • 解决思路:
    • 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>

引用块和公式块冲突

  • 问题描述

    • 在引用块中又写了公式块,最终渲染出这样:

    image-20251025152358827

  • 产生原因

    • 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,当你手动刷新才会变成相关页面

image-20251028102231171

image-20251028102345594

  • 产生原因
    • PJAX 通过 AJAX 加载新页面的内容(这里配置的 selectors: [".main-container"],只替换主容器),而不进行完整的页面重载(full page reload)。这意味着title部分没有进行更新。
  • 解决思路
    • 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:sendpjax: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>
Licensed under CC BY-NC-SA 4.0
最后更新于 2025-10-30
页面浏览量loading...
使用 Hugo 构建
主题 StackJimmy 设计