@parsonic/back-to-top
Version:
Scroll back to top of page web component
148 lines (141 loc) • 3.9 kB
JavaScript
// 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;
}
}
(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;
}
};
export {
BackToTop as default
};