LISN.js

Edit on StackBlitz

"use client";
import { useEffect, useRef } from "react";

import { ScrollWatcher, LoadTrigger, Show, AutoHide } from "lisn.js";
import "lisn.js/base.css";

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

export default function Page() {
  const msgRef = useRef(null);
  const demoRef = useRef(null);

  useEffect(() => {
    const watcher = ScrollWatcher.reuse();
    const main = demoRef.current;
    if (main) {
      watcher.trackScroll(null, {
        scrollable: main,
        threshold: 0,
      });
    }

    return () => {
      // cleanup
      if (main) {
        watcher.noTrackScroll(null, main);
      }
    };
  }, []);

  useEffect(() => {
    const msg = msgRef.current;
    let widget: AutoHide;
    if (msg) {
      new LoadTrigger(msg, [new Show(msg)], {
        delay: 1000,
      });
      widget = new AutoHide(msg, { delay: 2500 });
    }

    return () => {
      // cleanup
      widget?.destroy();
    };
  }, []);

  return (
    <>
      <div className={styles.wrapper}>
        <p ref={msgRef} className={[styles.msg, "lisn-hide"].join(" ")}>
          Scroll the box
        </p>

        <div ref={demoRef} className={styles.demo}>
          <div className={styles.spotlight}></div>

          <div className={styles.section}>
            <h1>L</h1>
            <h4>Lightweight.</h4>

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

          <div className={styles.section}>
            <h1>I</h1>
            <h4>Interactive.</h4>

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

          <div className={styles.section}>
            <h1>S</h1>
            <h4>Simple.</h4>

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

          <div className={styles.section}>
            <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>
    </>
  );
}
/* Container */
.wrapper {
  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;
}

.demo {
  height: 400px;
  width: 800px;
  max-width: 100vw;
  max-width: 100dvw;
  box-shadow: var(--lisn-shadow);
  color: var(--text-color);
  overflow: auto;
}

/* Background */
.spotlight {
  width: 25vh;
  height: 150vh;
  overflow: hidden;
  position: fixed;
  top: 0;
  left: 0;
  z-index: -1;
  background: linear-gradient(
    90deg,
    transparent 10%,
    var(--bg-color-lighter) 50%,
    transparent 90%
  );
  transform-origin: 0% 50%;
  transform: translate3d(
      calc(var(--lisn-js--scroll-top-fraction, 0) * 100vw),
      -5vh,
      0
    )
    rotate(-20deg);
  will-change: transform;
  transition-property: transform;
  transition-duration: 0.1s;
  transition-timing-function: linear;
}

/* Content */
.section {
  height: 400px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
}

.section::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: var(--bg-color-lighter);
  z-index: -2;
}

/* Misc styles */
.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 main = document.getElementById("demo");
  LISN.watchers.ScrollWatcher.reuse().trackScroll(null, {
    scrollable: main,
    threshold: 0,
  });

  const msg = document.getElementById("msg");
  new LISN.triggers.LoadTrigger(msg, [new LISN.actions.Show(msg)], {
    delay: 1000,
  });
  new LISN.widgets.AutoHide(msg, { delay: 1500 });
});
<p id="msg" class="lisn-hide">Scroll the box</p>

<div id="demo">
  <div class="spotlight"></div>

  <div class="section">
    <h1>L</h1>
    <h4>Lightweight.</h4>

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

  <div class="section">
    <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="section">
    <h1>S</h1>
    <h4>Simple.</h4>

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

  <div class="section">
    <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>
/* Container */
#demo {
  height: 400px;
  width: 800px;
  max-width: 100%;
  box-shadow: var(--lisn-shadow);
  color: var(--text-color);
  overflow: auto;
}

/* Background */
#demo .spotlight {
  width: 25vh;
  height: 150vh;
  overflow: hidden;
  position: fixed;
  top: 0;
  left: 0;
  z-index: -1;
  background: linear-gradient(
    90deg,
    transparent 10%,
    var(--bg-color-lighter) 50%,
    transparent 90%
  );
  transform-origin: 0% 50%;
  transform: translate3d(
      calc(var(--lisn-js--scroll-top-fraction, 0) * 100vw),
      -5vh,
      0
    )
    rotate(-20deg);
  will-change: transform;
  transition-property: transform;
  transition-duration: 0.1s;
  transition-timing-function: linear;
}

/* Content */
#demo .section {
  height: 400px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
}

#demo .section::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #28283f;
  z-index: -2;
}

#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

<p
  class="lisn-hide"
  data-lisn-on-load="@show +delay=1000"
  data-lisn-auto-hide="1500"
>
  Scroll the box
</p>

<div id="demo" data-lisn-track-scroll>
  <div class="spotlight"></div>

  <div class="section">
    <h1>L</h1>
    <h4>Lightweight.</h4>

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

  <div class="section">
    <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="section">
    <h1>S</h1>
    <h4>Simple.</h4>

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

  <div class="section">
    <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>
/* Container */
#demo {
  height: 400px;
  width: 800px;
  max-width: 100%;
  box-shadow: var(--lisn-shadow);
  color: var(--text-color);
  overflow: auto;
}

/* Background */
#demo .spotlight {
  width: 25vh;
  height: 150vh;
  overflow: hidden;
  position: fixed;
  top: 0;
  left: 0;
  z-index: -1;
  background: linear-gradient(
    90deg,
    transparent 10%,
    var(--bg-color-lighter) 50%,
    transparent 90%
  );
  transform-origin: 0% 50%;
  transform: translate3d(
      calc(var(--lisn-js--scroll-top-fraction, 0) * 100vw),
      -5vh,
      0
    )
    rotate(-20deg);
  will-change: transform;
  transition-property: transform;
  transition-duration: 0.1s;
  transition-timing-function: linear;
}

/* Content */
#demo .section {
  height: 400px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
}

#demo .section::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #28283f;
  z-index: -2;
}

#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;
}

Scroll the box

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