From 07c670c173719398d1e0a04345a1c2a6d4f3a5e4 Mon Sep 17 00:00:00 2001 From: Michel Binder <37824907+cubmic@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:00:58 +0200 Subject: [PATCH] feat(scroll-top): implement scroll-top component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -------- Co-authored-by: Michel Binder Co-authored-by: Dan Büschlen --- src/components/scroll-top/ScrollTop.js | 87 +++++++ src/components/scroll-top/leu-scroll-top.js | 6 + src/components/scroll-top/scroll-top.css | 34 +++ .../scroll-top/stories/scroll-top.stories.js | 217 ++++++++++++++++++ .../scroll-top/test/scroll-top.test.js | 22 ++ src/lib/utils.js | 24 +- 6 files changed, 387 insertions(+), 3 deletions(-) create mode 100644 src/components/scroll-top/ScrollTop.js create mode 100644 src/components/scroll-top/leu-scroll-top.js create mode 100644 src/components/scroll-top/scroll-top.css create mode 100644 src/components/scroll-top/stories/scroll-top.stories.js create mode 100644 src/components/scroll-top/test/scroll-top.test.js diff --git a/src/components/scroll-top/ScrollTop.js b/src/components/scroll-top/ScrollTop.js new file mode 100644 index 00000000..a6676313 --- /dev/null +++ b/src/components/scroll-top/ScrollTop.js @@ -0,0 +1,87 @@ +import { html, LitElement } from "lit" +import { classMap } from "lit/directives/class-map.js" + +import styles from "./scroll-top.css" +import "../button/leu-button.js" +import { throttle } from "../../lib/utils.js" + +/** + * @tagname leu-scroll-top + */ +export class LeuScrollTop extends LitElement { + static styles = styles + + static properties = { + _showButton: { state: true }, + } + + constructor() { + super() + /** @internal */ + this._prevYPos = 0 + /** @internal */ + this._showButton = false + /** @internal */ + this._scrollDown = false + + /** @internal */ + this._scrollListener = undefined + } + + scroll = () => { + const delta = window.scrollY - this._prevYPos + + if (this._scrollDown) { + if (delta < 0) { + this._scrollDown = false + } + } else if (delta > 0) { + this._scrollDown = true + } + + /** + * Only show the button when + * ... the current scroll position is greater than the window height (below-the-fold) and when + * ... scrolling up + */ + this._showButton = window.scrollY > window.innerHeight && !this._scrollDown + this._prevYPos = window.scrollY + } + + connectedCallback() { + super.connectedCallback() + this._scrollListener = throttle(this.scroll, 100) + document.addEventListener("scroll", this._scrollListener, true) + } + + disconnectedCallback() { + document.removeEventListener("scroll", this._scrollListener, true) + super.disconnectedCallback() + } + + static scrollToTop() { + window.scrollTo({ + top: 0, + left: 0, + behavior: "smooth", + }) + } + + render() { + const cssClasses = { + "scroll-top": true, + hide: !this._showButton, + } + return html` +
+ + +
+ ` + } +} diff --git a/src/components/scroll-top/leu-scroll-top.js b/src/components/scroll-top/leu-scroll-top.js new file mode 100644 index 00000000..398a241a --- /dev/null +++ b/src/components/scroll-top/leu-scroll-top.js @@ -0,0 +1,6 @@ +import { defineElement } from "../../lib/defineElement.js" +import { LeuScrollTop } from "./ScrollTop.js" + +export { LeuScrollTop } + +defineElement("scroll-top", LeuScrollTop) diff --git a/src/components/scroll-top/scroll-top.css b/src/components/scroll-top/scroll-top.css new file mode 100644 index 00000000..cf37edc9 --- /dev/null +++ b/src/components/scroll-top/scroll-top.css @@ -0,0 +1,34 @@ +.scroll-top { + overflow: hidden; + position: fixed; + right: 1.5rem; + z-index: 1099; + bottom: 8.125rem; + + /* show */ + height: 50px; + pointer-events: auto; + transition: height, bottom 0.9s, 0.6s ease; +} + +.hide { + height: 0; + pointer-events: none; + transition: height, top 0.9s, 0.6s ease; +} + +@keyframes hide-animation { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(90deg); + } +} + +.hide leu-button { + animation-name: hide-animation; + animation-duration: 0.45s; + animation-timing-function: ease; +} diff --git a/src/components/scroll-top/stories/scroll-top.stories.js b/src/components/scroll-top/stories/scroll-top.stories.js new file mode 100644 index 00000000..e6b2d049 --- /dev/null +++ b/src/components/scroll-top/stories/scroll-top.stories.js @@ -0,0 +1,217 @@ +import { html } from "lit" +import "../leu-scroll-top.js" + +export default { + title: "ScrollTop", + component: "leu-scroll-top", +} + +function Template() { + return html` +

+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy + eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam + voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet + clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit + amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam + nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed + diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. + Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor + sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed + diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam + erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea + rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum + dolor sit amet. +

+

+ Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse + molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero + eros et accumsan et iusto odio dignissim qui blandit praesent luptatum + zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum + dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh + euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. +

+

+ Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper + suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel + eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, + vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et + iusto odio dignissim qui blandit praesent luptatum zzril delenit augue + duis dolore te feugait nulla facilisi. +

+

+ Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet + doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit + amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod + tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad + minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis + nisl ut aliquip ex ea commodo consequat. +

+

+ Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse + molestie consequat, vel illum dolore eu feugiat nulla facilisis. +

+

+ At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd + gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem + ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod + tempor invidunt ut labore et dolore magna aliquyam erat, sed diam + voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet + clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit + amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam + aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed + tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna + no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor + sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed + diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam + erat. +

+

+ Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut + labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et + accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no + sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit + amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt + ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et + accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no + sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit + amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt + ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et + accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no + sea takimata sanctus. +

+

+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy + eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam + voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet + clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit + amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam + nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed + diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. + Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor + sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed + diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam + erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea + rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum + dolor sit amet. +

+

+ Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse + molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero + eros et accumsan et iusto odio dignissim qui blandit praesent luptatum + zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum + dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh + euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. +

+

+ Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper + suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel + eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, + vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et + iusto odio dignissim qui blandit praesent luptatum zzril delenit augue + duis dolore te feugait nulla facilisi. +

+

+ Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet + doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit + amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod + tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad + minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis + nisl ut aliquip ex ea commodo consequat. +

+

+ Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse + molestie consequat, vel illum dolore eu feugiat nulla facilisis. +

+

+ At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd + gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem + ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod + tempor invidunt ut labore et dolore magna aliquyam erat, sed diam + voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet + clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit + amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam + aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed + tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna + no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor + sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed + diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam + erat. +

+

+ Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut + labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et + accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no + sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit + amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt + ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et + accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no + sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit + amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt + ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et + accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no + sea takimata sanctus. +

+

+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy + eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam + voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet + clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit + amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam + nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed + diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. + Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor + sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed + diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam + erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea + rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum + dolor sit amet. +

+

+ Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse + molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero + eros et accumsan et iusto odio dignissim qui blandit praesent luptatum + zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum + dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh + euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. +

+

+ Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper + suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel + eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, + vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et + iusto odio dignissim qui blandit praesent luptatum zzril delenit augue + duis dolore te feugait nulla facilisi. +

+

+ Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet + doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit + amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod + tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad + minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis + nisl ut aliquip ex ea commodo consequat. +

+

+ Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse + molestie consequat, vel illum dolore eu feugiat nulla facilisis. +

+

+ At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd + gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem + ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod + tempor invidunt ut labore et dolore magna aliquyam erat, sed diam + voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet + clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit + amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam + aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed + tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna + no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor + sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed + diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam + erat. +

+ + ` +} + +export const Regular = Template.bind({}) diff --git a/src/components/scroll-top/test/scroll-top.test.js b/src/components/scroll-top/test/scroll-top.test.js new file mode 100644 index 00000000..ea04f22a --- /dev/null +++ b/src/components/scroll-top/test/scroll-top.test.js @@ -0,0 +1,22 @@ +import { html } from "lit" +import { fixture, expect } from "@open-wc/testing" + +import "../leu-scroll-top.js" + +async function defaultFixture() { + return fixture(html` `) +} + +describe("LeuScrollTop", () => { + it("is a defined element", async () => { + const el = await customElements.get("leu-scroll-top") + + await expect(el).not.to.be.undefined + }) + + it("passes the a11y audit", async () => { + const el = await defaultFixture() + + await expect(el).shadowDom.to.be.accessible() + }) +}) diff --git a/src/lib/utils.js b/src/lib/utils.js index 13f93f80..d67cf33c 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,10 +1,10 @@ /** - * Wrap a Function to nsure that time-consuming tasks do not fire so often + * Return a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked. * @param {Function} func - Your function * @param {Number} timeout - Default is 500 ms * @returns {Function} - Your function wrapped in a timeout function */ -const debounce = function (func, timeout = 500) { +const debounce = function debounce(func, timeout = 500) { let timer = null return (...args) => { clearTimeout(timer) @@ -14,4 +14,22 @@ const debounce = function (func, timeout = 500) { } } -export { debounce } +/** + * Return a throttled function that only invokes func at most once per every wait milliseconds. + * @param {Function} func - Your function + * @param {Number} timeout - Default is 500 ms + * @returns {Function} - Your function wrapped in a timeout function + */ +const throttle = function throttle(func, timeout = 500) { + let timer = null + return (...args) => { + if (timer === null) { + func(...args) + timer = setTimeout(() => { + timer = null + }, timeout) + } + } +} + +export { debounce, throttle }