LISN.js

Edit on StackBlitz

"use client";
import {
  PagerComponent,
  PagerPageComponent,
  PagerSwitchComponent,
  PagerPrevSwitchComponent,
  PagerNextSwitchComponent,
} from "@lisn.js/react";
// Don't load the default pager CSS otherwise we'd have to override quite a few
// properties. We need to set custom style for everything anyway.
// import "lisn.js/pager.css";

import styles from "./demo.module.css";

export default function Page() {
  return (
    <>
      <div className={styles.wrapper}>
        <PagerComponent
          className={styles.demo}
          config={{
            horizontal: true,
            useGestures: "touch,wheel",
            alignGestureDirection: true,
            preventDefault: false,
          }}
        >
          <div className={styles.arrows}>
            <PagerPrevSwitchComponent
              className={styles.arrow}
              data-switch="prev"
              aria-label="Previous"
            ></PagerPrevSwitchComponent>
            <PagerNextSwitchComponent
              className={styles.arrow}
              data-switch="next"
              aria-label="Next"
            ></PagerNextSwitchComponent>
          </div>

          <div className={styles.pages}>
            {/* Pages act as switches themselves so that the user can tap one
						to make it active */}
            <PagerPageComponent className={styles.page}>
              <PagerSwitchComponent className={styles.content}>
                <h1>L</h1>
                <h4>Lightweight.</h4>

                <ul>
                  <li>Vanilla TypeScript</li>
                  <li>Highly optimized</li>
                  <li>No layout thrashing</li>
                </ul>
              </PagerSwitchComponent>
            </PagerPageComponent>

            <PagerPageComponent className={styles.page}>
              <PagerSwitchComponent className={styles.content}>
                <h1>I</h1>
                <h4>Interactive.</h4>

                <ul>
                  <li>Powerful API</li>
                  <li>Multi gesture support</li>
                  <li>Mobile/touch ready</li>
                </ul>
              </PagerSwitchComponent>
            </PagerPageComponent>

            <PagerPageComponent className={styles.page}>
              <PagerSwitchComponent className={styles.content}>
                <h1>S</h1>
                <h4>Simple.</h4>

                <ul>
                  <li>Intuitive syntax</li>
                  <li>Consistent API</li>
                  <li>HTML-only mode</li>
                </ul>
              </PagerSwitchComponent>
            </PagerPageComponent>

            <PagerPageComponent className={styles.page}>
              <PagerSwitchComponent className={styles.content}>
                <h1>N</h1>
                <h4>No-nonsense.</h4>

                <ul>
                  <li>What says on the box</li>
                  <li>Sensible defaults</li>
                  <li>Highly customizable</li>
                </ul>
              </PagerSwitchComponent>
            </PagerPageComponent>
          </div>
        </PagerComponent>
      </div>
    </>
  );
}
/* Container */
.wrapper {
  --animate-duration: 0.4s;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  width: 100dvw;
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

/* Pager and page container */
.demo {
  width: 100%;
  padding: 0 3vmax;
}

.pages {
  --gap: 3vmin;
  --tot-p: var(--lisn-js--total-pages, 1);
  --curr-p: var(--lisn-js--current-page, 1);
  --vis-p: min(6, var(--tot-p));
  --page-w: calc(
    min(
      320px,
      (100% - var(--gap) * (var(--vis-p) - 1)) /
        (var(--vis-p) + min(var(--tot-p) - var(--vis-p), 0.5))
    )
  );
  max-width: calc(
    var(--tot-p) * var(--page-w) + (var(--vis-p) - 1) * var(--gap)
  );
  margin: 0 auto;
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: stretch;
  justify-content: flex-start;
  gap: var(--gap);
  overflow: visible;
  transition-duration: 0.7s;
  transition-property: transform;
  transform: translateX(
    calc(
      -1 * (var(--page-w) + var(--gap)) *
        max(
          0,
          min(
            var(--tot-p) - var(--vis-p) - 0.5,
            var(--curr-p) - 1.25 - (var(--vis-p) - 1) / 2
          )
        )
    )
  );
}

/* Individual pages */
.page {
  padding: 5vh 3vmin;
  border-radius: 15px;
  background: var(--bg-color-lighter);
  flex: 0 0 var(--page-w);
  height: auto;
  position: relative;
  transform: scale(0.92);
  transition-property: transform, flex-basis;
  transition-duration: 0.7s;
  box-shadow: rgba(0, 0, 0, 0.2) 4px 5px 5px 0px;
}

.page .content {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.page[data-lisn-page-state="current"] {
  background: var(--bg-color-lightest);
  transform: scale(1);
}

/* Switches (general) */

.content,
.arrow {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  outline: none;
}

/* Prev/next arrows */
.arrows {
  width: 100%;
  margin-bottom: 20px;
  display: flex;
  justify-content: center;
  gap: 10px;
}

.demo[data-lisn-current-page-is-first-enabled="true"] [data-switch="prev"],
.demo[data-lisn-current-page-is-last-enabled="true"] [data-switch="next"] {
  cursor: default;
  pointer-events: none;
  opacity: 0.25;
}

.arrow {
  background: none;
  padding: 0;
  margin: 10px;
  width: 0;
  height: 0;
  border-style: solid;
  opacity: 0.8;
  cursor: pointer;
  border-width: 13px;
  border-color: transparent var(--text-color);
}

.arrow:hover {
  opacity: 1;
}

.arrow[data-switch="prev"] {
  border-left: none !important;
}

.arrow[data-switch="next"] {
  border-right: none !important;
}

/* Number of visible pages on smaller screens */
@media (max-width: 1810px) {
  .pages {
    --vis-p: min(5, var(--tot-p));
  }
}

@media (max-width: 1530px) {
  .pages {
    --vis-p: min(4, var(--tot-p));
  }
}

@media (max-width: 1250px) {
  .pages {
    --vis-p: min(3, var(--tot-p));
  }
}

@media (max-width: 970px) {
  .pages {
    --vis-p: min(2, var(--tot-p));
  }
}

@media (max-width: 750px) {
  .pages {
    --vis-p: 1;
  }
}

/* Page content */
.demo h1 {
  margin: 0 auto;
  font-size: 75px;
  background-image: linear-gradient(
    45deg,
    var(--text-color) 42%,
    var(--text-color-lighter) 50%,
    var(--text-color) 58%
  );
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

.demo h4 {
  font-size: clamp(20px, calc(15px + 2vw), 32px);
}

.demo ul {
  padding: 0;
  list-style-type: none;
}

.demo ul li {
  margin: 8px 0;
}

.demo ul li::before {
  content: "\2726";
  display: inline-block;
  font-size: 0.4em;
  transform: translateY(-0.4em);
  margin: 0 0.6em 0 0;
}

Edit on CodePen

document.addEventListener("DOMContentLoaded", () => {
  const pager = document.getElementById("demo");
  const pages = pager.querySelectorAll(".page");
  const switches = pager.querySelectorAll(".switch");
  const prevSwitch = pager.querySelector(".prev-switch");
  const nextSwitch = pager.querySelector(".next-switch");
  new LISN.widgets.Pager(pager, {
    pages,
    switches,
    prevSwitch,
    nextSwitch,
    horizontal: true,
    useGestures: "touch,wheel",
    alignGestureDirection: true,
    preventDefault: false,
  });
});
<div id="demo">
  <div class="prev-next-buttons">
    <button class="prev-switch" aria-label="Previous"></button>
    <button class="next-switch" aria-label="Next"></button>
  </div>

  <div class="pages">
    <div class="page switch">
      <h1>L</h1>
      <h4>Lightweight.</h4>

      <ul>
        <li>Vanilla TypeScript</li>
        <li>Highly optimized</li>
        <li>No layout thrashing</li>
      </ul>
    </div>

    <div class="page switch">
      <h1>I</h1>
      <h4>Interactive.</h4>

      <ul>
        <li>Powerful API</li>
        <li>Multi gesture support</li>
        <li>Mobile/touch ready</li>
      </ul>
    </div>

    <div class="page switch">
      <h1>S</h1>
      <h4>Simple.</h4>

      <ul>
        <li>Intuitive syntax</li>
        <li>Consistent API</li>
        <li>HTML-only mode</li>
      </ul>
    </div>

    <div class="page switch">
      <h1>N</h1>
      <h4>No-nonsense.</h4>

      <ul>
        <li>What says on the box</li>
        <li>Sensible defaults</li>
        <li>Highly customizable</li>
      </ul>
    </div>
  </div>
</div>
/* Pager and page container */
#demo {
  width: 100%;
  padding: 0 3vmax;
}

#demo .pages {
  --gap: 3vmin;
  --tot-p: var(--lisn-js--total-pages, 1);
  --curr-p: var(--lisn-js--current-page, 1);
  --vis-p: min(6, var(--tot-p));
  --page-w: calc(
    min(
      320px,
      (100% - var(--gap) * (var(--vis-p) - 1)) /
        (var(--vis-p) + min(var(--tot-p) - var(--vis-p), 0.5))
    )
  );
  max-width: calc(
    var(--tot-p) * var(--page-w) + (var(--vis-p) - 1) * var(--gap)
  );
  margin: 0 auto;
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: stretch;
  justify-content: flex-start;
  gap: var(--gap);
  overflow: visible;
  transition-duration: 0.7s;
  transition-property: transform;
  transform: translateX(
    calc(
      -1 * (var(--page-w) + var(--gap)) *
        max(
          0,
          min(
            var(--tot-p) - var(--vis-p) - 0.5,
            var(--curr-p) - 1.25 - (var(--vis-p) - 1) / 2
          )
        )
    )
  );
}

/* Individual pages */
#demo .page {
  padding: 5vh 3vmin;
  border-radius: 15px;
  background: var(--bg-color-lighter);
  flex: 0 0 var(--page-w);
  height: auto;
  position: relative;
  transform: scale(0.92);
  display: flex;
  flex-direction: column;
  align-items: center;
  transition-property: transform, flex-basis;
  box-shadow: rgba(0, 0, 0, 0.2) 4px 5px 5px 0px;
}

#demo .page[data-lisn-page-state="current"] {
  background: var(--bg-color-lightest);
  transform: scale(1);
}

#demo .switch {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  outline: none;
}

/* Prev/next arrows */
#demo[data-lisn-current-page-is-first-enabled="true"] .prev-switch,
#demo[data-lisn-current-page-is-last-enabled="true"] .next-switch {
  --btn-color: #777;
  cursor: default;
  pointer-events: none;
}

#demo .prev-next-buttons {
  width: 100%;
  margin-bottom: 20px;
  display: flex;
  justify-content: center;
  gap: 10px;
}

#demo .prev-next-buttons button {
  --btn-color: #ddd;
  background: none;
  padding: 0;
  margin: 10px;
  width: 0;
  height: 0;
  border-style: solid;
  opacity: 0.8;
  cursor: pointer;
  border-width: 13px;
  border-color: transparent var(--btn-color);
}

#demo .prev-next-buttons button:hover {
  --btn-color: #fff;
}

#demo .prev-switch {
  border-left: none !important;
}

#demo .next-switch {
  border-right: none !important;
}

/* Number of visible pages on smaller screens */
@media (max-width: 1810px) {
  #demo .pages {
    --vis-p: min(5, var(--tot-p));
  }
}

@media (max-width: 1530px) {
  #demo .pages {
    --vis-p: min(4, var(--tot-p));
  }
}

@media (max-width: 1250px) {
  #demo .pages {
    --vis-p: min(3, var(--tot-p));
  }
}

@media (max-width: 970px) {
  #demo .pages {
    --vis-p: min(2, var(--tot-p));
  }
}

@media (max-width: 750px) {
  #demo .pages {
    --vis-p: 1;
  }
}

/* Page content */
#demo h1 {
  margin: 0 auto;
  font-size: 75px;
  background-image: linear-gradient(
    45deg,
    var(--text-color) 42%,
    var(--text-color-lighter) 50%,
    var(--text-color) 58%
  );
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

#demo h4 {
  font-size: clamp(20px, calc(15px + 2vw), 32px);
}

#demo ul {
  padding: 0;
  list-style-type: none;
}

#demo ul li {
  margin: 8px 0;
}

#demo ul li::before {
  content: "\2726";
  display: inline-block;
  font-size: 0.4em;
  transform: translateY(-0.4em);
  margin: 0 0.6em 0 0;
}

Edit on CodePen

<div
  id="demo"
  data-lisn-pager="horizontal
                 | use-gestures=touch,wheel
                 | align-gesture-direction=true
                 | prevent-default=false"
>
  <div class="prev-next-buttons">
    <button data-lisn-pager-prev-switch aria-label="Previous"></button>
    <button data-lisn-pager-next-switch aria-label="Next"></button>
  </div>

  <div class="pages">
    <div data-lisn-pager-page data-lisn-pager-switch>
      <h1>L</h1>
      <h4>Lightweight.</h4>

      <ul>
        <li>Vanilla TypeScript</li>
        <li>Highly optimized</li>
        <li>No layout thrashing</li>
      </ul>
    </div>

    <div data-lisn-pager-page data-lisn-pager-switch>
      <h1>I</h1>
      <h4>Interactive.</h4>

      <ul>
        <li>Powerful API</li>
        <li>Multi gesture support</li>
        <li>Mobile/touch ready</li>
      </ul>
    </div>

    <div data-lisn-pager-page data-lisn-pager-switch>
      <h1>S</h1>
      <h4>Simple.</h4>

      <ul>
        <li>Intuitive syntax</li>
        <li>Consistent API</li>
        <li>HTML-only mode</li>
      </ul>
    </div>

    <div data-lisn-pager-page data-lisn-pager-switch>
      <h1>N</h1>
      <h4>No-nonsense.</h4>

      <ul>
        <li>What says on the box</li>
        <li>Sensible defaults</li>
        <li>Highly customizable</li>
      </ul>
    </div>
  </div>
</div>
/* Pager and page container */
#demo {
  width: 100%;
  padding: 0 3vmax;
}

#demo .pages {
  --gap: 3vmin;
  --tot-p: var(--lisn-js--total-pages, 1);
  --curr-p: var(--lisn-js--current-page, 1);
  --vis-p: min(6, var(--tot-p));
  --page-w: calc(
    min(
      320px,
      (100% - var(--gap) * (var(--vis-p) - 1)) /
        (var(--vis-p) + min(var(--tot-p) - var(--vis-p), 0.5))
    )
  );
  max-width: calc(
    var(--tot-p) * var(--page-w) + (var(--vis-p) - 1) * var(--gap)
  );
  margin: 0 auto;
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: stretch;
  justify-content: flex-start;
  gap: var(--gap);
  overflow: visible;
  transition-duration: 0.7s;
  transition-property: transform;
  transform: translateX(
    calc(
      -1 * (var(--page-w) + var(--gap)) *
        max(
          0,
          min(
            var(--tot-p) - var(--vis-p) - 0.5,
            var(--curr-p) - 1.25 - (var(--vis-p) - 1) / 2
          )
        )
    )
  );
}

/* Individual pages */
#demo [data-lisn-pager-page] {
  padding: 5vh 3vmin;
  border-radius: 15px;
  background: var(--bg-color-lighter);
  flex: 0 0 var(--page-w);
  height: auto;
  position: relative;
  transform: scale(0.92);
  display: flex;
  flex-direction: column;
  align-items: center;
  transition-property: transform, flex-basis;
  box-shadow: rgba(0, 0, 0, 0.2) 4px 5px 5px 0px;
}

#demo [data-lisn-pager-page][data-lisn-page-state="current"] {
  background: var(--bg-color-lightest);
  transform: scale(1);
}

#demo [data-lisn-pager-switch] {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  outline: none;
}

/* Prev/next arrows */
#demo[data-lisn-current-page-is-first-enabled="true"]
  [data-lisn-pager-prev-switch],
#demo[data-lisn-current-page-is-last-enabled="true"]
  [data-lisn-pager-next-switch] {
  --btn-color: #777;
  cursor: default;
  pointer-events: none;
}

#demo .prev-next-buttons {
  width: 100%;
  margin-bottom: 20px;
  display: flex;
  justify-content: center;
  gap: 10px;
}

#demo .prev-next-buttons button {
  --btn-color: #ddd;
  background: none;
  padding: 0;
  margin: 10px;
  width: 0;
  height: 0;
  border-style: solid;
  opacity: 0.8;
  cursor: pointer;
  border-width: 13px;
  border-color: transparent var(--btn-color);
}

#demo .prev-next-buttons button:hover {
  --btn-color: #fff;
}

#demo [data-lisn-pager-prev-switch] {
  border-left: none !important;
}

#demo [data-lisn-pager-next-switch] {
  border-right: none !important;
}

/* Number of visible pages on smaller screens */
@media (max-width: 1810px) {
  #demo .pages {
    --vis-p: min(5, var(--tot-p));
  }
}

@media (max-width: 1530px) {
  #demo .pages {
    --vis-p: min(4, var(--tot-p));
  }
}

@media (max-width: 1250px) {
  #demo .pages {
    --vis-p: min(3, var(--tot-p));
  }
}

@media (max-width: 970px) {
  #demo .pages {
    --vis-p: min(2, var(--tot-p));
  }
}

@media (max-width: 750px) {
  #demo .pages {
    --vis-p: 1;
  }
}

/* Page content */
#demo h1 {
  margin: 0 auto;
  font-size: 75px;
  background-image: linear-gradient(
    45deg,
    var(--text-color) 42%,
    var(--text-color-lighter) 50%,
    var(--text-color) 58%
  );
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

#demo h4 {
  font-size: clamp(20px, calc(15px + 2vw), 32px);
}

#demo ul {
  padding: 0;
  list-style-type: none;
}

#demo ul li {
  margin: 8px 0;
}

#demo ul li::before {
  content: "\2726";
  display: inline-block;
  font-size: 0.4em;
  transform: translateY(-0.4em);
  margin: 0 0.6em 0 0;
}

L

Lightweight.

  • Vanilla TypeScript
  • Highly optimized
  • No layout thrashing

I

Interactive.

  • Powerful API
  • Multi gesture support
  • Mobile/touch ready

S

Simple.

  • Intuitive syntax
  • Consistent API
  • HTML-only mode

N

No-nonsense.

  • What says on the box
  • Sensible defaults
  • Highly customizable