LOADING

- 系统启动中

博客改造记录

用于记录博客改造的历程,主要是对 Stack 主题进行一些样式上的修改和功能上的增强。

前言

  • 首先介绍一下 Hugo 使用样式文件的基本优先级,Hugo 会优先使用网站根目录下 layouts\ 文件夹中的样式文件,之后使用模板中的样式文件,以 Stack 主题中的文章详情样式为例,它使用的模板是 themes\hugo-theme-stack\layouts\partials\article\components\details.html ,如果想要修改样式,则需要在同样的目录下创建同样的样式文件将其覆盖,即在 layouts\partials\article\components\ 目录下创建 details.html 文件,并将源文件中的代码复制过来。

  • PS:其实我一直认为它是合并,直到我调整鼠标样式时修改了 details.html 文件,才发现原来是覆盖。 缺少的文件手动创建或主题内复制。

更换字体

字体目录

  • layouts/partials/footer/custom.html 路径创建配置文件并拷贝以下代码

字体配置文件

虽然乌龙了,但是晓得了代码块下划线怎么用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<style>
@font-face {
font-family: '字体名';
src: url({{ (resources.Get "font/字体文件名(带后缀)").Permalink }}) format('truetype');
}

:root {
--base-font-family: '字体名';
--code-font-family: '字体名';
}
</style>
  • 上为原代码将导致字体文件无法加载, custom 文件报错(以下代码仅本地可用,服务器将导致字体丢失—原因为两者一个是动态引用一个是静态引用)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12

/_ 字体 _/
@font-face {
font-family: 'MarukoGothicCJKjp-Regular';
src: url('/font/MarukoGothicCJKjp-Regular.ttf') format('truetype');
}

:root {
--base-font-family: 'MarukoGothicCJKjp-Regular', sans-serif;
--code-font-family: 'MarukoGothicCJKjp-Regular', monospace;
}
/_ 字体 _/

更换鼠标样式

  • 逆天指针卡了我两个小时,也是够了,折腾了半天希望实现动态指针,通过网上找到的解析.ani 文件未.cur 并逐帧播放的方法,最终实现了动态指针效果。

  • But!!!效果不佳,和 ppt 一样有明显卡顿感,但确实能行,并且会和原本的动态鼠标样式混淆,所以还是放弃了,转而采用传统的静态图标的方法。

  • But!!!奇葩的情况出现了,一般鼠标指针都为.cur 或.ani 格式,所以在替换时并没有太注意,结果出现了问题,指针在我的浏览器巨大无比,经过漫长的排查,最后发现调整成.png 格式就恢复正常了。

  • 贴一下使用的链接:图片转换器

  • 步骤:

    • 鼠标指针图片放在 static/mouse 文件夹下
    • 修改 assets/scss/custom.scss 文件,填充以下代码
 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

// 【Stack 主题鼠标样式写法】
// default 光标图片
body,
html,
.article-content img {
cursor: url(../mouse/默认光标图片名),
auto !important;
}

// pointer 光标图片
a:hover,
button:hover,
.copyCodeButton:hover,
#dark-mode-toggle {
cursor: url(../mouse/指针光标图片名),
auto;
}

// text 光标图片
input:hover,
.site-description,
.article-subtitle,
.article-content span,
.article-content li,
.article-content p {
cursor: url(../mouse/文本光标图片名),
auto;
}

显示文章更新时间

  • 配置文件 hugo.yaml 下添加以下代码
1
2
3
4
5
6
7
8
9

# 更新时间:优先读取 git 时间 -> git 时间不存在,就读取本地文件修改时间

frontmatter:
lastmod: - :git - :fileModTime

# 允许获取 Git 信息

enableGitInfo: true
  • 修改 github action 文件 .github/workflows/xxx.yaml ,在运行 hugo -D 命令的 step 前加入以下配置
1
2
3
4
5
6
7

- name: Git Configuration
  run: |
  git config --global core.quotePath false
  git config --global core.autocrlf false
  git config --global core.safecrlf true
  git config --global core.ignorecase false

如图所示

  • 若想在文章开头就显示更新时间,修改 layouts/partials/article/components/details.html 文件,添加以下代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

<div class="article-details">
    ...
    <footer class="article-time">
        ...
        <!-- 更新时间 -->
        {{- if ne .Lastmod .Date -}}
            <div class="article-lastmod">
                {{ partial "helper/icon" "clock" }}
                <time>
                    {{ .Lastmod.Format ( or .Site.Params.dateFormat.lastUpdated "Jan 02, 2006 15:04 MST" ) }}
                </time>
            </div>
        {{- end -}}
        ....
    </footer>
    ...
</div>

显示文章字数统计

  • layouts\partials\article\components\details.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
37
    ...
    <footer class="article-time">
        {{ if $showDate }}
            <div>
                {{ partial "helper/icon" "date" }}
                <time class="article-time--published">
                    {{- .Date | time.Format (or .Site.Params.dateFormat.published "Jan 02, 2006") -}}
                </time>
            </div>
        {{ end }}


    <!-- 在此插入以下代码 -->

        {{ if $showReadingTime }}
        <div>
            {{ partial "helper/icon" "wordcount" }}
            <time class="article-time--reading">
                {{ T "article.wordCount" .WordCount  }}
            </time>
        </div>
        {{ end }}

    <!-- 在此结束 -->


        {{ if $showReadingTime }}
            <div>
                {{ partial "helper/icon" "clock" }}
                <time class="article-time--reading">
                    {{ T "article.readingTime" .ReadingTime }}
                </time>
            </div>
        {{ end }}
    </footer>
    {{ end }}
    ...
  • i18n\en.toml 文件中的 article: 类下添加以下代码
1
2
3
    # 以下为新增代码
  wordCount:
    other: "本文共 {{ .Count }} 字"
  • assets\icons\ 文件夹下创建 wordcount.svg

  • 本站使用图标

1
2
3
4
5
6
7
8
9
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-text-count" width="56" height="56" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
  <path stroke="none" d="M0 0h24v24H0z"/>

  <!-- 纸张背景 -->
  <rect x="4" y="4" width="16" height="16" rx="2" ry="2" />
  <path d="M8 8h8" />
  <path d="M8 12h8" />
  <path d="M8 16h8" />
</svg>
  • 注意!stack 主题用的适量图标均为无填充,粗线条构建,所以如果发现自己找来的图标不符合要求,不要傻傻的去试图更改颜色适配!!!

友链、归档多列显示

  • 修改 assets/scss/custom.scss 文件,引入以下 css 样式代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@media (min-width: 1024px) {
  .article-list--compact {
    display: grid;
    // 目前是两列,如需三列,则后面再加一个1fr,以此类推
    grid-template-columns: 1fr 1fr 1fr;
    background: none;
    box-shadow: none;
    gap: 1rem;

    article {
      background: var(--card-background);
      border: none;
      box-shadow: var(--shadow-l2);
      margin-bottom: 8px;
      margin-right: 8px;
      border-radius: 16px;
    }
  }
}

文章目录自动折叠

  • 将以下代码复制到 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
37
38
39
40
41
42
43
44
45
46
<style>
    #TableOfContents > ul, ol {
        ul, ol {
            display: none;
        }
        .open {
            display: block;
        }
    }
</style>

<script>
    function initTocHide() {
        // 判断是否存在文章目录
        let toc = document.querySelector(".widget--toc");
        if (!toc) {
            return;
        }
        // 监听滚动
        window.addEventListener('scroll', function() {
            //清除class值
            let openUl = document.querySelectorAll(".open");
            if (openUl.length > 0) {
              openUl.forEach((ul) => {
                ul.classList.remove("open")
              })
            }
            // 获取active-class
            let currentLi = document.querySelector(".active-class");
            if (!currentLi) {
                return
            }
            // 展示子ul
            if (currentLi.children.length > 1) {
                currentLi.children[1].classList.add("open")
            }
            // 展示父ul
            let ul = currentLi.parentElement;
            do {
                ul.classList.add("open");
                ul = ul.parentElement.parentElement
            } while (ul !== undefined && (ul.localName === 'ul' || ul.localName === 'ol'))
        });
    }
    initTocHide()
</script>
  • 文章就会默认隐藏子目录,等滚动到对应的目录后,才会将子目录进行展示。

动态头像

  • 准备合适大小的 gif 图片,如 你的名字.gif,将其放入 static/img 文件夹下(gif 过大将导致重影)

  • hugo.yaml 文件中更改配置 local 改为 flaseavatar 改为 你的名字.gif

  • 补充:静态头像放在 assets/img 下(和原主题保持一致),此处放在 static/img 下是为了避开 Hugo 默认的资源处理(static 目录下不会被处理)。

  • Stack 主题默认会在 assets/ 或 static/ 中查找头像文件,并通过 Hugo Pipes 处理(添加哈希值后缀—资源指纹,处理压缩—方便缓存,提高缓存速率)。

  • local 设为 false 时,头像也可以直接采用 url 路径,如 avatar: "https://xxx.com/your-avatar.png"

如图所示

动态背景

  • 樱花飞舞背景

  • 下载【sakura.js】(Ctrl + S 保存),并放到 assets/background 文件夹下

  • layouts/partials/footer/custom.html 中,引入以下代码

1
<script src={{ (resources.Get "background/sakura.js").Permalink }}></script>

代码收缩

  • 准备一张向下展开图片(Ctrl+S 保存),放到 assets/icons 目录下

  • 将以下代码复制进 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<style>
    .highlight {
        /* 你可以根据需要调整这个高度 */
        max-height: 400px;
        overflow: hidden;
    }

    .code-show {
        max-height: none !important;
    }

    .code-more-box {
        width: 100%;
        padding-top: 78px;
        background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(255, 255, 255, 0)), to(#fff));
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 1;
    }

    .code-more-btn {
        display: block;
        margin: auto;
        width: 44px;
        height: 22px;
        background: #f0f0f5;
        border-top-left-radius: 8px;
        border-top-right-radius: 8px;
        padding-top: 6px;
        cursor: pointer;
    }

    .code-more-img {
        cursor: pointer !important;
        display: block;
        margin: auto;
        width: 22px;
        height: 16px;
    }
</style>

<script>
  //代码收缩
  function initCodeMoreBox() {
      let codeBlocks = document.querySelectorAll(".highlight");
      if (!codeBlocks) {
        return;
      }
      codeBlocks.forEach(codeBlock => {
        // 校验是否overflow
        if (codeBlock.scrollHeight <= codeBlock.clientHeight) {
          return;
        }
        // 元素初始化
        // codeMoreBox
        let codeMoreBox = document.createElement('div');
        codeMoreBox.classList.add('code-more-box');
        // codeMoreBtn
        let codeMoreBtn = document.createElement('span');
        codeMoreBtn.classList.add('code-more-btn');
        codeMoreBtn.addEventListener('click', () => {
          codeBlock.classList.add('code-show');
          codeMoreBox.style.display = 'none';
          // 触发resize事件,重新计算目录位置
          window.dispatchEvent(new Event('resize'))
        });
        // img
        let img = document.createElement('img');
        img.classList.add('code-more-img');
        // 使用Hugo模板语法获取图片路径
        img.src = '{{ (resources.Get "icons/codeMore.png").Permalink }}';

        // 元素添加
        codeMoreBtn.appendChild(img);
        codeMoreBox.appendChild(codeMoreBtn);
        codeBlock.appendChild(codeMoreBox);
      });
  }
  //代码收缩

  initCodeMoreBox();
</script>

返回顶部

  • 准备一张返回顶部图片(Ctrl+S 保存),放到 assets/icons

  • 将以下代码复制到 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
37
38
39
40
41
42
43
44
45
46
47
<style>
    #backTopBtn {
        display: none;
        position: fixed;
        bottom: 30px;
        z-index: 99;
        cursor: pointer;
        width: 30px;
        height: 30px;
        background-image: url({{ (resources.Get "icons/backTop.svg").Permalink }});
    }
</style>

<script>
    /**
     * 滚动回顶部初始化
     */
    function initScrollTop() {
        let rightSideBar = document.querySelector(".right-sidebar");
        if (!rightSideBar) {
            return;
        }
        // 添加返回顶部按钮到右侧边栏
        let btn = document.createElement("div");
        btn.id = "backTopBtn";
        btn.onclick = backToTop
        rightSideBar.appendChild(btn)
        // 滚动监听
        window.onscroll = function() {
            // 当网页向下滑动 20px 出现"返回顶部" 按钮
            if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
                btn.style.display = "block";
            } else {
                btn.style.display = "none";
            }
        };
    }

    /**
     * 返回顶部
     */
    function backToTop(){
        window.scrollTo({ top: 0, behavior: "smooth" })
    }

    initScrollTop();
</script>

点击特效

  • 获取网上下载的点击特效

  • 创建新的 js 文件,如 click.js放在 assets/click-effect 文件夹下

  • 将以下代码复制进 click.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
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/**
 * 鼠标点击和长按特效函数
 * 创建一个全屏canvas,实现点击和长按的粒子动画效果
 */
function clickEffect() {
    let balls = [];             // 存储所有粒子的数组
    let longPressed = false;    // 是否长按的标志
    let longPress;              // 长按定时器
    let multiplier = 0;         // 长按乘数,影响粒子大小和数量
    let width, height;          // canvas宽高
    let origin;                 // 原点位置
    let normal;                 // 法线向量
    let ctx;                    // canvas上下文

    // 粒子颜色数组
    const colours = ["#F73859", "#14FFEC", "#00E0FF", "#FF99FE", "#FAF15D"];

    // 创建全屏canvas元素并添加到页面
    const canvas = document.createElement("canvas");
    document.body.appendChild(canvas);
    canvas.setAttribute("style", "width: 100%; height: 100%; top: 0; left: 0; z-index: 99999; position: fixed; pointer-events: none;");

    // 创建鼠标指针元素
    const pointer = document.createElement("span");
    pointer.classList.add("pointer");
    document.body.appendChild(pointer);

    // 检查浏览器支持
    if (canvas.getContext && window.addEventListener) {
      ctx = canvas.getContext("2d");
      updateSize();
      window.addEventListener('resize', updateSize, false);
      loop();

      // 鼠标按下事件 - 点击或长按的开始
      window.addEventListener("mousedown", function(e) {
        // 点击时创建10-20个粒子
        pushBalls(randBetween(10, 20), e.clientX, e.clientY);
        document.body.classList.add("is-pressed");

        // 设置长按定时器(500ms) - 可调整长按检测时间
        longPress = setTimeout(function(){
          document.body.classList.add("is-longpress");
          longPressed = true;
        }, 500); // 长按检测时间 - 可调整持续时间
      }, false);

      // 鼠标释放事件 - 点击或长按的结束
      window.addEventListener("mouseup", function(e) {
        clearInterval(longPress);
        if (longPressed == true) {
          document.body.classList.remove("is-longpress");
          // 长按结束时创建更多粒子(50-100 + multiplier)
          pushBalls(randBetween(50 + Math.ceil(multiplier), 100 + Math.ceil(multiplier)), e.clientX, e.clientY);
          longPressed = false;
        }
        document.body.classList.remove("is-pressed");
      }, false);

      // 鼠标移动事件 - 更新指针位置
      window.addEventListener("mousemove", function(e) {
        let x = e.clientX;
        let y = e.clientY;
        pointer.style.top = y + "px";
        pointer.style.left = x + "px";
      }, false);
    } else {
      console.log("canvas or addEventListener is unsupported!");
    }

    /**
     * 更新canvas尺寸,适应窗口变化
     */
    function updateSize() {
      // 高分辨率渲染
      canvas.width = window.innerWidth * 2;
      canvas.height = window.innerHeight * 2;
      canvas.style.width = window.innerWidth + 'px';
      canvas.style.height = window.innerHeight + 'px';
      ctx.scale(2, 2);

      width = window.innerWidth;
      height = window.innerHeight;
      origin = {
        x: width / 2,
        y: height / 2
      };
      normal = {
        x: width / 2,
        y: height / 2
      };
    }

    /**
     * 粒子类 - 表示特效中的单个粒子
     */
    class Ball {
      constructor(x = origin.x, y = origin.y) {
        this.x = x;                 // 粒子x坐标
        this.y = y;                 // 粒子y坐标
        this.angle = Math.PI * 2 * Math.random();  // 粒子发射角度

        // 粒子速度乘数 - 长按会产生更大更快的粒子
        if (longPressed == true) {
          this.multiplier = randBetween(10 + multiplier, 12 + multiplier);
        } else {
          this.multiplier = randBetween(6, 12);
        }

        // 粒子速度分量
        this.vx = (this.multiplier + Math.random() * 0.5) * Math.cos(this.angle);
        this.vy = (this.multiplier + Math.random() * 0.5) * Math.sin(this.angle);



        // 粒子初始半径 - 可调整粒子大小
        this.r = randBetween(4, 6) + 2 * Math.random(); // 可调整粒子大小




        // 随机选择粒子颜色
        this.color = colours[Math.floor(Math.random() * colours.length)];
      }

      /**
       * 更新粒子位置和属性
       */
      update() {
        this.x += this.vx - normal.x;
        this.y += this.vy - normal.y;
        normal.x = -2 / window.innerWidth * Math.sin(this.angle);
        normal.y = -2 / window.innerHeight * Math.cos(this.angle);

        // 粒子半径减小 - 控制粒子消失速度(持续时间)
        this.r -= 0.3; // 可调整粒子消失速度(持续时间)

        // 速度衰减
        this.vx *= 0.9;
        this.vy *= 0.9;
      }
    }

    /**
     * 创建指定数量的粒子
     * @param {number} count - 粒子数量
     * @param {number} x - 起始x坐标
     * @param {number} y - 起始y坐标
     */
    function pushBalls(count = 1, x = origin.x, y = origin.y) {
      for (let i = 0; i < count; i++) {
        balls.push(new Ball(x, y));
      }
    }

    /**
     * 生成指定范围内的随机整数
     * @param {number} min - 最小值
     * @param {number} max - 最大值
     * @returns {number} - 随机整数
     */
    function randBetween(min, max) {
      return Math.floor(Math.random() * max) + min;
    }

    /**
     * 动画循环 - 每一帧更新和绘制所有粒子
     */
    function loop() {
      // 清空画布
      ctx.fillStyle = "rgba(255, 255, 255, 0)";
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // 绘制并更新所有粒子
      for (let i = 0; i < balls.length; i++) {
        let b = balls[i];
        if (b.r < 0) continue;

        ctx.fillStyle = b.color;
        ctx.beginPath();
        ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2, false);
        ctx.fill();
        b.update();
      }

      // 长按乘数更新
      if (longPressed == true) {
        multiplier += 0.2;
      } else if (!longPressed && multiplier >= 0) {
        multiplier -= 0.4;
      }

      // 移除离开画布或半径小于0的粒子
      removeBall();

      // 请求下一帧动画
      requestAnimationFrame(loop);
    }

    /**
     * 移除超出边界或不可见的粒子
     */
    function removeBall() {
      for (let i = 0; i < balls.length; i++) {
        let b = balls[i];
        if (b.x + b.r < 0 || b.x - b.r > width || b.y + b.r < 0 || b.y - b.r > height || b.r < 0) {
          balls.splice(i, 1);
        }
      }
    }
  }

  // 调用特效函数
  clickEffect();
  • 和背景的 js 一样在 layouts/partials/footer/custom.html 中引入
1
<script src={{ (resources.Get "click-effect/click.js" ).Permalink }}></script>
  • 这样就实现了鼠标点击和长按的粒子动画效果,该 js 已被添加注释,可以根据注释改动参数和效果。

评论系统

  • 目前博客使用的是 giscus 评论系统(这个主题内置了多个评论系统)。

  • 直接跟着官方文档走即可giscus

  • 最后改一下配置文件就行了。(想进一步改动的可以了解下,改动 giscus.html 即可)

小 BUG 处理

  • github action 提示 you can try to increase the ’timeout’ config setting.

  • 由于 github action 编译时间超过了 hugo 允许的编译时间导致的提交失败(action 中有错误提示和解决方法)

  • 在如图位置添加 time:6000s 即可(数值自行调整)

  • 参考链接

Made with ❤️ by KKPT
最后更新于 Oct 30, 2025 18:00 +0800
使用 Hugo 构建
主题 StackJimmy 设计