<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. */
  • Content:
    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;
    }
    
  • URL: /components/raw/scroll-to-top/scroll-to-top-util.js
  • Filesystem Path: src/components/scroll-to-top/scroll-to-top-util.js
  • Size: 852 Bytes
  • Content:
    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();
    
  • URL: /components/raw/scroll-to-top/scroll-to-top.js
  • Filesystem Path: src/components/scroll-to-top/scroll-to-top.js
  • Size: 237 Bytes
  • Content:
    @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;
    }
    
  • URL: /components/raw/scroll-to-top/scroll-to-top.scss
  • Filesystem Path: src/components/scroll-to-top/scroll-to-top.scss
  • Size: 1.1 KB

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();

Parameters

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.

Return

The scrollToTop function returns the dynamically generated DOM node of the button:

const button = scrollToTop.default();
button.addEventListener("click", () => console.log("scroll!"));

Example

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.