<link media="all" rel="stylesheet" href="/scroll-to-top/scroll-to-top.css" />
<script src="/scroll-to-top/scroll-to-top.js"></script>
{# no html, the button is generated via js #}
{% import "_macros.twig" as h %}
{{ h.componentAssets('scroll-to-top') }}
/* No context defined. */
export default function ScrollToTop(anchor = "main", container, hidden = true) {
/*
<a href="#" class="scrollToTop">
<i class="fa-solid fa-angle-up scrollToTop__icon"></i>
<span class="u-srOnly">Back to </span>Top
</a>
*/
const button = document.createElement("a");
button.classList.add("scrollToTop");
button.setAttribute("href", `#${anchor}`);
const icon = document.createElement("i");
icon.classList.add("fa-solid", "fa-angle-up", "scrollToTop__icon");
const span = document.createElement("span");
span.classList.add("u-srOnly");
span.innerText = "Back to ";
const text = document.createTextNode("Top");
button.appendChild(icon);
button.appendChild(span);
button.appendChild(text);
if (!hidden) {
button.classList.add("scrollToTop--visible");
}
container.appendChild(button);
return button;
}
import "./scroll-to-top.scss";
import * as scrollToTop from "./scroll-to-top-util";
export default function ScrollToTop() {
const prose = document.querySelector(".prose");
scrollToTop.default("main", prose, false);
}
ScrollToTop();
@use "../_layout/mixins";
$buttonTransitions: color 0.2s ease-out, background-color 0.2s ease-out,
border-color 0.2s ease-out;
.scrollToTop {
@include mixins.button();
--button-bg: var(--accent);
--button-color: var(--blue--ultraDark);
--button-border-color: var(--blue--ultraDark);
--button-size: var(--step-0);
padding: 0 0.5em;
position: fixed;
bottom: var(--offsetBottom, 2em);
right: var(--offsetRight, 2em);
z-index: calc(var(--z) * 20);
display: flex;
flex-direction: column;
text-align: center;
text-transform: uppercase;
font-weight: var(--bold-weight);
max-width: 6em;
visibility: hidden;
opacity: 0;
transition: $buttonTransitions, visibility 0.3s, opacity 0.2s ease-out;
&:hover,
&:focus,
&:active {
opacity: 1;
}
}
.scrollToTop__icon {
margin-block-start: -0.05em;
margin-block-end: -0.2em;
font-size: var(--step-4);
--color: var(--button-color);
@include mixins.boldIcon();
}
.scrollToTop--visible {
visibility: visible;
opacity: 0.6;
transition: $buttonTransitions, visibility 0s, opacity 0.2s ease-out;
}
The button’s HTML is generated by the JS, so the only thing that is required to add this component is to just import the JS:
import "components/scroll-to-top/scroll-to-top.scss";
import * as scrollToTop from "components/scroll-to-top/scroll-to-top-util";
scrollToTop.default();The scrollToTop function takes 3 parameters:
anchor {string} "main" This is the id of the element to where the scroll button should link.
container {DOMElement} required The DOM node where the scroll button will be inserted via appendChild.
hidden {boolean} true Whether or not to init the button with the .scrollToTop--visible class.
The scrollToTop function returns the dynamically generated DOM node of the button:
const button = scrollToTop.default();
button.addEventListener("click", () => console.log("scroll!"));You can use the Intersection Observer API to show/hide the button after a certain amount of page scrolls:
import "components/scroll-to-top/scroll-to-top.scss";
import * as scrollToTop from "components/scroll-to-top/scroll-to-top-util";
const button = scrollToTop.default();
let observer = new IntersectionObserver(
//callback function
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
button.classList.remove("scrollToTop--visible");
} else {
button.classList.add("scrollToTop--visible");
}
});
},
//options
{
root: null,
rootMargin: "200% 0% 0% 0%",
threshold: 1,
}
);
const target = document.getElementById("js-siteHeader");
observer.observe(target);The above code imports and creates the button on the page (but hidden by default on init). Then an observer is created so that once #js-siteHeader is moved up by two screen heights (passes 200% of the viewport), the scroll button becomes visible. This essentially allows the scroll button to because visible after a user scrolls their screen by two page lengths, regardless of the device dimensions.