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