UNPKG

@parsonic/back-to-top

Version:

Scroll back to top of page web component

148 lines (140 loc) 3.9 kB
// src/style.css var sheet = new CSSStyleSheet(); sheet.replaceSync(`:host { --button-background: var(--btt-button-background, rgb(0 0 0 / 60%)); --button-background-hover: var( --btt-button-background-hover, rgb(0 0 0 / 80%) ); --button-border: var(--btt-button-border, none); --button-color: var(--btt-button-color, white); --button-inset: var(--btt-button-inset, auto 2rem 2rem auto); --button-padding: var(--btt-button-padding, 0.5rem); --button-radius: var(--btt-button-radius, 999px); --button-size: var(--btt-button-size, 1.5rem); inset: var(--button-inset); opacity: 0; position: fixed; transform: scale(0); transition: transform 200ms, opacity 200ms; visibility: hidden; } :host([data-state='active']) { opacity: 1; transform: scale(1); visibility: visible; } button { background: var(--button-background); border: var(--button-border); border-radius: var(--button-radius); box-sizing: content-box; color: var(--button-color); cursor: pointer; font-size: var(--button-size); height: var(--button-size); line-height: 1; padding: var(--button-padding); transition: background 200ms; width: var(--button-size); svg { position: relative; top: -1px; } } @media (hover: hover) { button:hover { background: var(--button-background-hover); } } `); var style_default = sheet; // src/BackToTop.js var BackToTop = class extends HTMLElement { /** * Defines the custom element with provided tag name */ static register(tagName = "back-to-top") { customElements.define(tagName, this); } #controller = null; #scrollPosition = 0; #activationPoint = 500; connectedCallback() { const { buttonLabel = "Scroll back to top", scrollBehavior = "auto", scrollContainer, threshold } = this.dataset; const template = document.createElement("template"); template.innerHTML = `<slot> <button part="button" type="button" aria-label="${buttonLabel}"> <slot name="icon"> <svg part="icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="m18 15-6-6-6 6"/> </svg> </slot> </button> </slot>`; const shadow = this.attachShadow({ mode: "open" }); shadow.appendChild(template.content.cloneNode(true)); shadow.adoptedStyleSheets.push(style_default); const activationPoint = parseInt(threshold, 10); if (!isNaN(activationPoint)) { this.#activationPoint = activationPoint; } let container; if (scrollContainer && document.getElementById(scrollContainer)) { container = document.getElementById(scrollContainer); } this.#setState(container?.scrollTop ?? window.scrollY); this.#controller = new AbortController(); const target = container ?? window; target.addEventListener( "scroll", () => { this.#setState(container?.scrollTop ?? window.scrollY); }, { signal: this.#controller.signal } ); shadow.querySelector("slot").addEventListener("click", () => { const { focusTarget } = this.dataset; if (focusTarget) { document.getElementById(focusTarget)?.focus({ preventScroll: true }); } target.scrollTo({ top: 0, behavior: scrollBehavior }); }); } disconnectedCallback() { this.#controller?.abort(); } #setState(scrollPosition) { if (scrollPosition > this.#activationPoint && scrollPosition < this.#scrollPosition) { this.dataset.state = "active"; } else { this.dataset.state = "inactive"; } this.#scrollPosition = scrollPosition; } }; // src/index.js BackToTop.register();