A heavy theatre curtain in WebGL in front of a full-bleed photograph: crimson velvet hanging in deep vertical folds, each face turned to the viewer staying dark and saturated while the faces turned away catch a soft rim of light, with fine vertical pile, a weighted hem and gathered swags across the top. As it opens each half draws to its side, the folds bunching tighter and deeper, the hem trailing the top and lifting off the floor, a soft shadow falling on the stage while the house lights come up on the photo. The fabric breathes slowly and sways where the cursor brushes past. Progress can be driven from scroll (a number or a MotionValue, read every frame without re-renders) or left to a slow open, hold and close loop. Color, sheen, folds, swags, the width kept at the sides and grain are props; it pauses offscreen and holds the open stage for reduced motion.
The import and the props worth knowing about, in one place.
import { CurtainReveal } from "@/components/beste/component/curtain-reveal";
<CurtainReveal className="h-[32rem] w-full" />
// Driven by scroll: pass a number from 0 (closed) to 1 (open), or a framer-motion MotionValue
const { scrollYProgress } = useScroll({ target: ref, offset: ["start start", "end end"] });
<CurtainReveal
className="absolute inset-0"
imageSrc="/photos/stage.jpg"
imageAlt="The orchestra on stage"
progress={scrollYProgress}
autoplay={false}
color="#1d2a4a" // any CSS color
sheenColor="#8fa6d8"
folds={14}
valance={0} // no swags across the top
frame={0} // draw the curtain fully out of view
/>Read from the component's own type, so this cannot drift from what it accepts.
| Prop | Type | Default | Description |
|---|---|---|---|
imageSrc | string | — | The photograph behind the curtain |
imageAlt | string | — | |
color | string | — | Velvet color, any CSS color |
sheenColor | string | — | Color of the light caught along the fold edges |
folds | number | — | Folds across each half of the curtain when it is closed |
sheen | number | — | Strength of the velvet sheen, 0 to 1 |
valance | number | — | Depth of the gathered swags across the top, 0 to 0.25 of the height; 0 removes them |
frame | number | — | Width each half keeps at the side once fully open, 0 to 0.2; 0 draws them out of view |
grain | number | — | Film grain, 0 to 1 |
progress | number | CurtainProgressSource | — | How far open, 0 closed to 1 open. A number, or a live source such as a scroll MotionValue |
autoplay | boolean | — | Open, hold and close on a slow loop. On by default when no progress is given; when on, it wins over progress |
speed | number | — | Pace of the loop and of the fabric's breathing |
interactive | boolean | — | The fabric sways where the cursor brushes past it |
paused | boolean | — | Freeze the fabric where it is |
className | string | — | |
children | ReactNode | — |