Featured image of post Hugo 博客功能拓展

Hugo 博客功能拓展

博客搭建完成后,来探索一下Hugo的更多有趣玩法

博客搭建过程见Hugo + Github博客搭建记录

点赞

创建点赞数据库

  • 注册并登录LeanCloud 控制台

  • 点击创建应用,填写名称,选择开发版(免费)

  • 点击应用左下角数据存储,然后点击创建Class
  • 类名填Like,注意首字母大写,其他设置如下
  • 点击like→添加列
字段名 类型 描述
path String 页面唯一路径
count Number 点赞数量
  • 点击左下角设置→应用凭证,记录自己的AppIDAppKeyAPI 服务器地址

设置

  • Tabler Icons下载点赞图标命名为thumb-up,放在static\icons
  • layouts\partials下创建like.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
 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
{{ if eq .Kind "page" }}
  <div id="like-container">
    <img id="like-icon" src="/icons/thumb-up.svg" alt="Like" />
    <span id="like-count">0</span>
  </div>

  <style>
    #like-container {
      text-align: center;
      margin: -2.5rem 0 -5rem 0rem;
      user-select: none;
      color: white;
    }

    #like-icon {
      width: 33px;
      height: 36px;
      cursor: pointer;
      transition: transform 0.15s ease;
      filter: brightness(0) invert(1);
    }

    #like-icon.clicked {
      animation: bounce 0.4s ease;
    }

    @keyframes bounce {
      0%   { transform: scale(1); }
      50%  { transform: scale(1.4); }
      100% { transform: scale(1); }
    }

    #like-count {
      display: inline-block;
      margin-left: 0px;
      font-size: 1.5rem; 
    }
  </style>

  <script>
  (function () {
    const appId = 'QMq4HFQshcHbTkGqUoX10lDK-gzGzoHsz';
    const appKey = 'MgjYDq8Uwr9g8x8HEJ24tyEB';
    const serverURL = 'https://qmz4hfqs.lc-cn-n1-shared.com';

    const pagePath = window.location.pathname;
    const localKey = 'liked_' + pagePath;
    const maxLikes = 50;
    const headers = {
      'X-LC-Id': appId,
      'X-LC-Key': appKey,
      'Content-Type': 'application/json'
    };

    async function fetchLike() {
      const res = await fetch(`${serverURL}/1.1/classes/Like?where=` + encodeURIComponent(JSON.stringify({ path: pagePath })), { headers });
      const data = await res.json();
      if (data.results.length > 0) {
        return data.results[0];
      } else {
        const resNew = await fetch(`${serverURL}/1.1/classes/Like`, {
          method: 'POST',
          headers,
          body: JSON.stringify({
            path: pagePath,
            count: 0,
            ACL: {
              "*": {
                "read": true,
                "write": true
              }
            }
          })
        });
        return await resNew.json();
      }
    }

    async function increaseLike(objectId) {
      await fetch(`${serverURL}/1.1/classes/Like/${objectId}`, {
        method: 'PUT',
        headers,
        body: JSON.stringify({ count: { __op: 'Increment', amount: 1 } })
      });
    }

    document.addEventListener('DOMContentLoaded', async () => {
      const icon = document.getElementById('like-icon');
      const count = document.getElementById('like-count');
      const likedCount = parseInt(localStorage.getItem(localKey) || '0');

      const data = await fetchLike();
      count.textContent = data.count || 0;

      icon.addEventListener('click', async () => {
        let current = parseInt(localStorage.getItem(localKey) || '0');
        if (current >= maxLikes) return;

        icon.classList.add('clicked');
        setTimeout(() => icon.classList.remove('clicked'), 400);

        count.textContent = parseInt(count.textContent) + 1;
        localStorage.setItem(localKey, current + 1);
        await increaseLike(data.objectId);
      });
    });
  })();
  </script>
{{ end }}
  • layouts\_default\single.html中, {{ partial "comments/include" . }}这一句之后添加{{ partial "like.html" . }}

背景

动态球

  • layouts\partials\footer\custom.html中加入
1
2
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/87/three.js"></script>
<script src="https://cdn.jsdelivr.net/gh/theme-next/theme-next-three@latest/canvas_sphere.min.js" ></script>

粒子

  • 这里自行设置,detect_on要改成 window,然后下载配置文件与js文件
  • 解压文件,将 particles.min.jsparticlesjs-config.json 放在 assets/background 文件夹下
  • layouts/partials/footer/custom.html 中加入
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<div id="particles-js"></div>

<script src={{ (resources.Get "background/particles.min.js").Permalink }}></script>
<script>
  particlesJS.load('particles-js', {{ (resources.Get "background/particlesjs-config.json").Permalink }}, function() {
    console.log('particles.js loaded - callback');
  });
</script>

<style>
  #particles-js {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    z-index: -1;
  }
</style>

底部小鱼

  • renderer.js保存到static/js/下。
  • 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
<div id="jsi-flying-fish-container"></div>

<script src="{{ "/js/renderer.js" | relURL }}"></script>

<style>
    #jsi-flying-fish-container {
        width: 100%;
        height: 200px;
        position: absolute;
        bottom: 0;
        left: 0;
        z-index: -1;
        opacity: 0.37;
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }
	    html, body {
      	  position: relative;
   	      min-height: 100%;
        }  
    /* 确保主要内容区域是相对定位的容器 */
    body {
        position: relative;
    }
</style>

<script>
  if (typeof RENDERER !== 'undefined') {
    RENDERER.init();
  }
</script>

音乐

我希望音乐可以作为背景音乐,自动播放并循环,不受页面刷新影响,同时保持博客界面整洁。故没有采用音乐插件

  • \hugo\blog\layouts\partials\footer\footer.html中加入以下代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<audio id="bg-music" src="/music/Low Roar - I'll Keep Coming.mp3" autoplay loop style="display:none;"></audio>

<script>
  const audio = document.getElementById("bg-music");

  // 浏览器策略限制,等待用户交互后尝试播放
  document.addEventListener('DOMContentLoaded', function () {
    // 尝试播放
    const playPromise = audio.play();

    if (playPromise !== undefined) {
      playPromise.then(() => {
        console.log("音乐播放成功");
      }).catch((error) => {
        console.log("自动播放被浏览器阻止,等待用户点击");
        // 用户交互后再尝试播放
        document.addEventListener('click', () => {
          audio.play();
        }, { once: true });
      });
    }
  });
</script>
  • 将mp3文件放在\static\music目录下

代码块折叠

  • 展开图片放到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
<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');
      img.src = {{ (resources.Get "icons/codeMore.png").Permalink }}
      // 元素添加
      codeMoreBtn.appendChild(img);
      codeMoreBox.appendChild(codeMoreBtn);
      codeBlock.appendChild(codeMoreBox)
    })
  }
  
  initCodeMoreBox();
</script>

字体

  • 100font下载字体,然后把文件放到assets/font

  • 粘贴以下代码到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/字体名.ttf").Permalink }}) format('truetype');
  } /这部分可以复制粘贴并修改名字增加字体

  :root {
    --base-font-family: '字体名';	/文章字体
    --code-font-family: '字体名';	/代码块字体
  }
</style>

鼠标样式

  • 将鼠标样式图片放入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
// 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;
}
Like 0
May you find your worth in the waking world.
使用 Hugo 构建
主题 StackJimmy 设计