From 1f82608cd6f2f349351b08849a7e0b2bbb6effb3 Mon Sep 17 00:00:00 2001 From: Morten Victor Nordbye Date: Sun, 16 Aug 2026 23:14:45 +0200 Subject: [PATCH] Add reading progress bar for articles (#3014) * Add reading progress bar for articles * Let page-level showReadingProgress override the site setting Hugo's default treats false as empty, so a page-level false could not disable the bar once it was enabled site-wide. Resolve the value by initialising from the site setting and overriding it only when the front matter parameter is actually present, and use the same resolved boolean in single.html and head.html. * Guard the reading progress handler and mark the listener passive The script loads in the head without defer, so the scroll listener is registered before the progress bar element exists. A scroll event fired during parsing, such as when the browser restores the scroll position on reload, would throw. Bail out when the element is missing, matching the guard in background-blur.js, and mark the scroll listener passive as toc.html already does. --- assets/css/compiled/main.css | 3 +++ assets/js/reading-progress.js | 9 +++++++++ config/_default/params.toml | 1 + exampleSite/config/_default/params.toml | 1 + exampleSite/content/docs/configuration/index.md | 1 + exampleSite/content/docs/front-matter/index.md | 1 + layouts/_default/single.html | 7 +++++++ layouts/partials/head.html | 11 +++++++++++ layouts/partials/reading-progress.html | 4 ++++ 9 files changed, 38 insertions(+) create mode 100644 assets/js/reading-progress.js create mode 100644 layouts/partials/reading-progress.html diff --git a/assets/css/compiled/main.css b/assets/css/compiled/main.css index f790eda9..b828eb2f 100644 --- a/assets/css/compiled/main.css +++ b/assets/css/compiled/main.css @@ -1080,6 +1080,9 @@ .h-0 { height: 0px; } + .h-1 { + height: var(--spacing); + } .h-1\/2 { height: calc(1 / 2 * 100%); } diff --git a/assets/js/reading-progress.js b/assets/js/reading-progress.js new file mode 100644 index 00000000..478ffa8a --- /dev/null +++ b/assets/js/reading-progress.js @@ -0,0 +1,9 @@ +function readingProgress() { + const bar = document.getElementById("reading-progress"); + if (!bar) return; + const scrollable = document.documentElement.scrollHeight - window.innerHeight; + bar.style.width = scrollable > 0 ? `${(window.scrollY / scrollable) * 100}%` : "0%"; +} + +window.addEventListener("scroll", readingProgress, { passive: true }); +window.addEventListener("load", readingProgress); diff --git a/config/_default/params.toml b/config/_default/params.toml index 44be887a..0b5f992d 100644 --- a/config/_default/params.toml +++ b/config/_default/params.toml @@ -96,6 +96,7 @@ forgejoDefaultServer = "https://v11.next.forgejo.org" showPagination = true invertPagination = false showReadingTime = true + showReadingProgress = false showTableOfContents = false # showRelatedContent = false # relatedContentLimit = 3 diff --git a/exampleSite/config/_default/params.toml b/exampleSite/config/_default/params.toml index 208a6f8c..167ebf02 100644 --- a/exampleSite/config/_default/params.toml +++ b/exampleSite/config/_default/params.toml @@ -83,6 +83,7 @@ fingerprintAlgorithm = "sha512" # Valid values are "sha512" (default), "sha384", showPagination = true invertPagination = false showReadingTime = true + showReadingProgress = true showTableOfContents = true showRelatedContent = true relatedContentLimit = 9 diff --git a/exampleSite/content/docs/configuration/index.md b/exampleSite/content/docs/configuration/index.md index 4ba7b549..3a0db546 100644 --- a/exampleSite/content/docs/configuration/index.md +++ b/exampleSite/content/docs/configuration/index.md @@ -278,6 +278,7 @@ Many of the article defaults here can be overridden on a per article basis by sp | `article.showPagination` | `true` | Whether or not the next/previous article links are displayed in the article footer. | | `article.invertPagination` | `false` | Whether or not to flip the direction of the next/previous article links. | | `article.showReadingTime` | `true` | Whether or not article reading times are displayed. | +| `article.showReadingProgress` | `false` | When set to `true` a reading progress bar is displayed at the top of articles. | | `article.showTableOfContents` | `false` | Whether or not the table of contents is displayed on articles. | | `article.showRelatedContent` | `false` | Display related content for each post. Might required additional configuration to your `hugo.toml`. Please check the theme `hugo.toml` if you want to enable this feature and copy all the relevant _related_ entries. Also check [Hugo's docs](https://gohugo.io/content-management/related/) on related content. | | `article.relatedContentLimit` | `3` | Limit of related articles to display if `showRelatedContent` is turned on. | diff --git a/exampleSite/content/docs/front-matter/index.md b/exampleSite/content/docs/front-matter/index.md index ffe2f2e5..41253cc2 100644 --- a/exampleSite/content/docs/front-matter/index.md +++ b/exampleSite/content/docs/front-matter/index.md @@ -42,6 +42,7 @@ Front matter parameter default values are inherited from the theme's [base confi | `showPagination` | `article.showPagination` | Whether or not the next/previous article links are displayed in the article footer. | | `invertPagination` | `article.invertPagination` | Whether or not to flip the direction of the next/previous article links. | | `showReadingTime` | `article.showReadingTime` | Whether or not the article reading time is displayed. | +| `showReadingProgress` | `article.showReadingProgress` | Whether or not the reading progress bar is displayed on this article. | | `showTaxonomies` | `article.showTaxonomies` | Whether or not the taxonomies that relate to this article are displayed. | | `showTableOfContents` | `article.showTableOfContents` | Whether or not the table of contents is displayed on this article. | | `showWordCount` | `article.showWordCount` | Whether or not the article word count is displayed. | diff --git a/layouts/_default/single.html b/layouts/_default/single.html index 95389091..c2fc57e1 100644 --- a/layouts/_default/single.html +++ b/layouts/_default/single.html @@ -1,5 +1,12 @@ {{ define "main" }} {{ .Scratch.Set "scope" "single" }} + {{ $showReadingProgress := site.Params.article.showReadingProgress | default false }} + {{ if isset .Params "showreadingprogress" }} + {{ $showReadingProgress = .Params.showReadingProgress }} + {{ end }} + {{ if $showReadingProgress }} + {{ partial "reading-progress.html" . }} + {{ end }}
{{/* Hero */}} {{ if .Params.showHero | default (site.Params.article.showHero | default false) }} diff --git a/layouts/partials/head.html b/layouts/partials/head.html index 18d69719..53129837 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -158,6 +158,17 @@ {{ $zoomJS := resources.Get "lib/zoom/zoom.min.umd.js" | resources.Fingerprint $alg }} {{ end }} + {{ $showReadingProgress := site.Params.article.showReadingProgress | default false }} + {{ if isset .Params "showreadingprogress" }} + {{ $showReadingProgress = .Params.showReadingProgress }} + {{ end }} + {{ if and .IsPage $showReadingProgress }} + {{ $jsReadingProgress := resources.Get "js/reading-progress.js" | resources.Minify | resources.Fingerprint $alg }} + + {{ end }} {{/* JS deferred */}} {{ $jsResources := slice }} diff --git a/layouts/partials/reading-progress.html b/layouts/partials/reading-progress.html new file mode 100644 index 00000000..7678b2c4 --- /dev/null +++ b/layouts/partials/reading-progress.html @@ -0,0 +1,4 @@ +