This post serves as a personal note of my journey building this site. The code and reflections here are primarily for learning and may not always be correct or production-ready.
When experimenting with my blog, I thought a floating Table of Contents (TOC) button for mobile devices would be a great idea (especially for post with massive contents) . However, after searching online, I couldn’t find much information on an existing implementation. So, I decided to break it down into smaller steps, starting with a simpler feature: building a back-to-top button.
Here’s how I did it, step by step.
Adding the HTML for the Button
I’m using the Stack theme, which has a convenient custom.html
file located in layouts/footer
. This file is perfect for adding custom features without modifying the theme’s core files.
Here’s the code I added:
<div id="back-to-top">
<button>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
</button>
</div>
Styling the button with CSS
To make the button visually appealing and adapt to both light and dark themes, I updated scss/partials/footer.scss
. Here’s the CSS:
#back-to-top {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
display: none;
}
#back-to-top button {
padding: 8px;
border: none;
border-radius: 8px;
background-color: var(--back-to-top-bg);
box-shadow: 0 3px 5px var(--back-to-top-shadow);
cursor: pointer;
width: 36px;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
}
#back-to-top button:hover {
background-color: var(--back-to-top-bg-hover);
box-shadow: 0 5px 8px var(--back-to-top-shadow-hover);
transform: translateY(-2px);
}
#back-to-top button svg {
width: 16px;
height: 16px;
// fill: var(--back-to-top-arrow);
stroke: var(--back-to-top-arrow);
transition: stroke 0.3s ease;
}
#back-to-top button:hover svg {
// fill: var(--back-to-top-arrow);
stroke: var(--back-to-top-arrow);
}
I updated variables.scss
to define the color variables for light and dark themes, as well as for a good separation:
/*
* Footer back to top style
*/
:root {
--back-to-top-bg: #f9f9fc;
--back-to-top-bg-hover: #ececf6;
--back-to-top-arrow: #2c3e50;
--back-to-top-shadow: rgba(0, 0, 0, 0.1);
--back-to-top-shadow-hover: rgba(0, 0, 0, 0.15);
}
[data-scheme="dark"] {
--back-to-top-bg: #424242;
--back-to-top-bg-hover: #383838;
--back-to-top-arrow: rgba(255, 255, 255, 0.7);
--back-to-top-shadow: rgba(0, 0, 0, 0.3);
--back-to-top-shadow-hover: rgba(0, 0, 0, 0.5);
}
Bringing the button to life with JS
The next step was making the button functional. I wanted it to:
- Show or hide based on scroll position (where you can modify the value).
- Scroll smoothly to the top when clicked.
To achieve this, I added the following JavaScript in footer/components/script.html
:
<script>
document.addEventListener("DOMContentLoaded", function () {
const backToTop = document.getElementById("back-to-top");
// Show or hide the button on scroll
window.addEventListener("scroll", function () {
if (window.scrollY > 400) {
backToTop.style.display = "block";
} else {
backToTop.style.display = "none";
}
});
// Scroll to the top
backToTop.addEventListener("click", function () {
window.scrollTo({ top: 0, behavior: "smooth" });
});
});
</script>
Reflections and Takeaways
One interesting takeaway from this practice was learning about the advantages of using addEventListener
over inline event handlers like onscroll
or onclick
. addEventListener
- supports Multiple Event Listeners, meaning in the future we can add other functions to the same event without overwriting existing handlers.
- allows cleaner code by separating event registration from HTML and other JavaScript logic.
Here the function is called twice, both have distinct roles.
The outer addEventListener("DOMContentLoaded")
ensures the script runs after the DOM is fully loaded and parsed. This is crucial because document.getElementById("back-to-top")
might return null
if the DOM hasn’t been built yet. Without this, subsequent code would fail.
The inner addEventListener("scroll")
listens for the user scrolling the page. It dynamically toggles the button’s visibility based on the scroll position, ensuring it appears only when needed.
Useful links
While searching realization online, I found some useful links for reference.
- How TO - Scroll Back To Top Button
- B1ain’s Blog: 返回顶部按钮
- Adding a Simple “Scroll to the Top” Button to Your Hugo Site The author used anchor and CSS only for the button.