目錄

在 LoveIt 主題的文章中新增相關文章區塊

前言

最近在翻閱文章時,發現閱讀完某文章後,要找到類似的文章是很麻煩的一件事。也由於在查找資料時,看到其他網站都有「類似文章」、「你可能也想看」等區塊,因此萌生在個人網站製作「相關文章」區塊的想法。

既然是相關文章,那總是需要知道那些文章會相似,對吧?但需要重新標記所有文章,或大費周章使用模型分類是一件很累(很蠢?)的事。在看到我的文章都有使用標籤或是分類進行標記時,就想到:「為什麼不就地取材呢?」因此,決定使用現有的標記作為分類依據。

以下介紹如何在每篇文章中增加「相關文章」區塊。

首先,於網站根目錄的 /layouts/partials/single/ 位置新增 related-carousel.html 檔案,並填入如下程式碼。

需要注意的是,在第 4 行需要填入一個圖片位置,當該文章沒有封面圖片時,會自動抓取一個預設的圖片。

而在先前的文章在 Hugo 中將文章上鎖中,我們為網站新增一個可以將文章上鎖的功能。在這裡,我不希望相關文章出現需要使用密碼才能閱讀的文章,因此第 15 行將這類文章過濾掉。

  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
{{ $current := . }}
{{ $category := index .Params.categories 0 }}
{{ $tags := .Params.tags }}
{{ $defaultImg := "你的圖片 url" | absURL }}

{{/* 取得有相同 categories 或 tags 的文章 */}}
{{ $relatedByCategory := where .Site.RegularPages "Params.categories" "intersect" (slice $category) }}
{{ $relatedByTags := where .Site.RegularPages "Params.tags" "intersect" $tags }}

{{/* 合併兩者並去除重複,還要排除當前頁面 */}}
{{ $combined := union $relatedByCategory $relatedByTags }}
{{ $related := where $combined "Permalink" "ne" .Permalink }}

{{/* 依條件過濾:password 為空,且 draft / hidden 標記為 false 或無 */}}
{{ $filtered := where $related "Params.password" "in" (slice "" nil) }}
{{ $filtered = where $filtered "Draft" false }}
{{ $filtered = where $filtered "Params.draft" false }}
{{ $filtered = where $filtered "Params.hiddenFromHomePage" false }}
{{ $filtered = where $filtered "Params.hiddenFromSearch" false }}

{{/* 想要顯示的最新文章數 */}}
{{ $n := 10 }}

{{/* 依日期排序(由新到舊) */}}
{{ $sorted := sort $filtered "Date" "desc" }}

{{/* 取前 $n 篇 */}}
{{ $latest := first $n $sorted }}

{{ if gt (len $latest) 0 }}

<section class="related-carousel-container" id="related-posts">
<h3 class="related-carousel-title">
  <a href="#related-posts" title="{{ T "relatedPosts" }}">{{ T "relatedPosts" }}</a>
</h3>
  <div class="carousel-wrapper">
    <button class="carousel-button prev"></button>
    <div class="carousel" id="related-carousel">
      {{ range $page := $latest }}
        <a class="carousel-card" href="{{ $page.RelPermalink }}">
            <img
            src={{ $defaultImg }}
            data-src="
            {{- with $page.Params.featuredImage -}}
                {{- $image := . -}}
                {{- if or (strings.HasPrefix $image "http") (strings.HasPrefix $image "https") -}}
                    {{- $image | safeURL -}}
                {{- else if (strings.HasPrefix $image "/") -}}
                    {{- $image | safeURL -}}
                {{- else -}}
                    {{- $filename := path.Base $image -}}
                    {{- with $page.Resources.Match (printf "**/%s" $filename) -}}
                        {{- (index . 0).RelPermalink -}}
                    {{- else -}}
                        {{- $defaultImg -}}
                    {{- end -}}
                {{- end -}}
            {{ else }}
                {{ $defaultImg }}
            {{ end }}"
            alt="{{ $page.Title }}"
            class="lazyload"
            >
            <div class="card-title">{{ $page.Title }}</div>
            <div class="card-date">{{ $page.Date.Format "2006-01-02" }}</div>
        </a>
        {{ end }}
    </div>
    <button class="carousel-button next"></button>
  </div>
</section>
{{ end }}

<script>
document.addEventListener('DOMContentLoaded', function () {
  const carousel = document.getElementById('related-carousel');
  const container = document.querySelector('.related-carousel-container');
  if (!carousel || !container) return;

  function updateJustify() {
    const cards = carousel.querySelectorAll('.carousel-card');
    let totalWidth = 0;

    cards.forEach(card => {
      const style = window.getComputedStyle(card);
      const marginRight = parseFloat(style.marginRight) || 0;
      totalWidth += card.offsetWidth + marginRight;
    });

    const containerWidth = container.clientWidth;

    if (totalWidth <= containerWidth) {
      carousel.classList.add('centered');
    } else {
      carousel.classList.remove('centered');
    }
  }

  // 初始化與 resize 時都判斷
  updateJustify();
  window.addEventListener('resize', updateJustify);

  // 左右按鈕控制
  const prevBtn = document.querySelector('.carousel-button.prev');
  const nextBtn = document.querySelector('.carousel-button.next');

  if (prevBtn && nextBtn) {
    prevBtn.addEventListener('click', () => {
      carousel.scrollBy({ left: -220, behavior: 'smooth' });
    });

    nextBtn.addEventListener('click', () => {
      carousel.scrollBy({ left: 220, behavior: 'smooth' });
    });
  }
});
</script>

新增 css 美化

新增程式碼篩選文章後,接下來要為這個功能增加一些美化。為了符合 Hugo LoveIt 主題的樣式,以下使用白色以及圓角作為裝飾,同時支援深色模式。

到網站根目錄的 /assets/css/_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
 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
// 相關文章輪播
.related-carousel-title {
  margin-top: 0;
  margin-bottom: 0.5rem;
  padding: 0;
  //font-size: 1.5rem;
  font-weight: bold;
  color: $global-font-color;

  [theme=dark] & {
    color: $global-font-color-dark;
  }
}

.related-carousel-container {
  margin-top: 3rem;
  padding: 1rem;
  background: #f9f9f9;
  [theme=dark] & {
    background: #242424;
  }
}

.carousel-wrapper {
  position: relative;
  overflow: hidden;
}

.carousel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scroll-behavior: smooth;
  justify-content: flex-start;
  gap: 1rem;
}

.carousel.centered {
  justify-content: center;
}

.carousel::-webkit-scrollbar {
  display: none;
}

.carousel-card {
  flex: 0 0 auto;
  width: 200px;
  scroll-snap-align: start;
  text-decoration: none;
  color: inherit;
  background: white;
  border-radius: 5px;
  overflow: hidden;
  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
  [theme=dark] & {
    background: #080808;
  }
}

.carousel-card img {
  width: 100%;
  height: 120px;
  object-fit: cover;
}

.card-title {
  font-size: 0.95rem;
  padding: 0.5rem;
  font-weight: bold;
}

.card-date {
  font-size: 0.8rem;
  padding: 0 0.5rem 0.5rem;
  color: gray;
}

.carousel-button {
  display: block;
  position: absolute;
  top: 40%;
  border: none;
  font-size: 2rem;
  padding: 0 0.5rem;
  cursor: pointer;
  z-index: 2;
  color: $global-font-color;
  background-color: rgba(255,255,255,0.8);

  &:hover {
    color: $global-link-hover-color;
  }

  [theme=dark] & {
    color: $global-font-color-dark;
    background: #000000;

    &:hover {
        color: $global-link-hover-color-dark;
    }
  }
}

.carousel-button.prev { left: 0; }
.carousel-button.next { right: 0; }
// 相關文章輪播

新增翻譯鍵

接下來,為了讓多語言網站能夠支援不同的文字選項,我們需要在語言檔案新增以下鍵值。

到網站根目錄的 /i18n/ 資料夾,找到欲修改的 語言代碼.toml ,新增如下鍵值。以下以 zh-tw.toml 示範。

zh-tw.toml

1
2
3
4
# === custom at related carousel ===
[relatedPosts]
other = "相關文章"
# === custom at related carousel ===

在文章模板插入相關文章

最後,將模板路徑新增到文章鐘就大功告成了!到網站根目錄的 /layouts/posts/single.html 檔案,找到 {{- /* Footer */ -}} ,並在下方增加如下程式碼。

1
2
3
4
5
        {{- /* Footer */ -}}
        {{- partial "single/footer.html" . -}}

        {{- /* Related Carousel */ -}}                    <!-- 新增這個 -->
        {{- partial "single/related-carousel.html" . -}}  <!-- 新增這個 -->

這樣就完成相關文章區塊了。

結語

這一次成功新增相關文章的區塊,讓在閱讀文章的時候能夠更快找到下一篇想要閱讀的文章,不僅美觀更是兼顧了方便性!想嘗試這個功能可以看看下方的相關文章區塊👇!

運行環境

  • Hugo 0.145.0
  • LoveIt 主題(2025 年 2 月 21 日 Github 上的版本)