ScrollVars / fx
docs · demo · bench · llms.txt · GitHub

Curtain

Two panels slide apart as you scroll through a pinned stretch.

Use it for: Chapter breaks, big reveals, section transitions.
Knobs: outer height = scroll length; panel content/colors are yours

revealed
scroll
vars

Install: npx scrollvars add curtain
Styles: import 'scrollvars/styles/pin.css'

<div data-sv data-sv-pin="250vh">
  <div class="sv-stage">
    <div class="grid h-full place-items-center">revealed content</div>
    <div class="sv-curtain-l absolute inset-y-0 left-0 w-1/2 bg-zinc-900"></div>
    <div class="sv-curtain-r absolute inset-y-0 right-0 w-1/2 bg-zinc-900"></div>
  </div>
</div>
<div data-sv data-sv-pin="250vh">     <!-- the pin helper owns the height -->
  <div class="sv-stage">                <!-- the sticky viewport, from pin.css -->
    <div class="revealed">…</div>
    <div class="panel sv-curtain-l">…</div>
    <div class="panel sv-curtain-r">…</div>
  </div>
</div>

/* the preset (styles/pin.css), plus the two panels, which are yours: */
.sv-stage .panel { position: absolute; inset: 0; width: 50%; background: #14141a; }
.sv-stage .sv-curtain-r { left: 50%; }
.sv .sv-curtain-l { translate: calc(var(--sv-pin, 0) * -101%) 0; }
.sv .sv-curtain-r { translate: calc(var(--sv-pin, 0) * 101%) 0; }

/* the same sheet's reduced-motion override, last so it wins on source order: */
@media (prefers-reduced-motion: reduce) {
  .sv .sv-curtain-l,
  .sv .sv-curtain-r { display: none; }
}
/* the same under html[data-sv-motion="reduce"], the site's own switch */
:where([data-sv-motion="reduce"]) .sv .sv-curtain-l, :where([data-sv-motion="reduce"]) .sv .sv-curtain-r { display: none; }
<Track pin="250vh">
  <div className="sv-stage">
    <Revealed />
    <div className="sv-curtain-l …" />
    <div className="sv-curtain-r …" />
  </div>
</Track>

Engine: npm i scrollvars, 12.9 KB gzip as ESM (this page's fx/sv.js IIFE: 13.1 KB). All effects respect prefers-reduced-motion and render complete without JS.