Scroll-driven CSS
Reveals, progress bars and other effects from two CSS variables, with no JavaScript.
vidscroll exposes the scroll position as CSS variables, so most visual effects need no JavaScript and cause no React re-renders:
| Variable | Set on | Goes from 0 to 1 |
|---|---|---|
–video-progress | The ScrollVideo block | Across the whole video |
–progress | Each Section | Across that section’s range (0 before it, 1 after) |
Both are inherited, so anything inside can use them. They follow the smoothed scroll, the same motion as the video.
Example.tsx
import { ScrollVideo, Section } from "vidscroll";
const css = `
.iris__mask {
position: absolute;
inset: 0;
--r: calc(var(--progress) * 80vmax);
background: radial-gradient(circle, transparent var(--r), #000 var(--r));
}
.iris h2 {
position: relative;
opacity: calc(1 - var(--progress) * 2);
}
.progress-bar {
position: absolute;
inset: auto 0 0 0;
height: 3px;
background: #f6b98e;
transform-origin: left;
transform: scaleX(var(--video-progress));
}
`;
export default function Example() {
return (
<ScrollVideo src="/train.mp4" length="400vh">
<style>{css}</style>
<Section id="iris" start={0} end={0.2} className="iris">
<div className="iris__mask" />
<h2>The Commute</h2>
</Section>
<div className="progress-bar" />
</ScrollVideo>
);
}A progress bar
A bar along the bottom that fills as you scroll through the video:
Hero.tsx
<ScrollVideo src="/videos/hero.mp4" length="400vh">
<div className="progress-bar" />
</ScrollVideo>styles.css
.progress-bar {
position: absolute;
inset: auto 0 0 0;
height: 3px;
background: white;
transform-origin: left;
transform: scaleX(var(--video-progress));
}transform: scaleX() is cheaper to animate than width, which matters on
phones.
A circular reveal
A section at the start whose black mask opens like a camera iris:
Hero.tsx
<Section start={0} end={0.2} className="iris">
<div className="iris__mask" />
<h2>The Commute</h2>
</Section>styles.css
.iris__mask {
position: absolute;
inset: 0;
--r: calc(var(--progress) * 80vmax);
background: radial-gradient(circle, transparent var(--r), #000 var(--r));
}
.iris h2 {
position: relative;
opacity: calc(1 - var(--progress) * 2);
}Mapping progress to any value
Most effects are one calc() away:
| Effect | CSS |
|---|---|
| Move up 80px into place | translate: 0 calc((1 - var(–progress)) * 80px) |
| Fade in over the first half | opacity: clamp(0, var(–progress) * 2, 1) |
| Fade out over the second half | opacity: clamp(0, 2 - var(–progress) * 2, 1) |
| Grow from 90% to 100% | scale: calc(0.9 + var(–progress) * 0.1) |
| One full turn over the video | rotate: calc(var(–video-progress) * 1turn) |
For effects that need JavaScript, such as changing numbers or SVG paths, see Live values.
Related
- Section: ranges and active state.
- Text synced to the video: fades and staggered lines.