scroll-cinema
v2.0.0
Published
Apple-style frame-by-frame scroll animator. Zero dependencies, single HTML file. Export to Vanilla JS, React, Vue 3, or plain HTML.
Maintainers
Readme
scroll-cinema
Apple-style frame-by-frame scroll animator — zero dependencies, single file, works offline.
→ Live Tool — open index.html in any browser.
What it does
Turn any video or PNG/JPG frame sequence into the cinematic scroll animation seen on Apple product pages — where a video plays frame-by-frame as the user scrolls. Export production-ready code in 4 formats.
Video / frames → Configure → Text layers → Export codeQuick start
# Open the GUI tool instantly
npx scroll-cinema open
# Or serve it properly (full canvas support, recommended)
npx scroll-cinema serveThis opens http://localhost:4242 with the full ScrollCinema v2 GUI.
Features
| | |
|---|---|
| 🎬 | Video → Frames — drop MP4/WebM, extracts frames in browser (no FFmpeg needed) |
| ✦ | Keyframe text layers — headlines, subtitles, counters at exact frame positions |
| 〜 | 9 easing curves — Spring, Elastic, Bounce, Expo with visual previews |
| 🔢 | Count-up numbers — 0→N as user scrolls (Apple's battery life trick) |
| 🎭 | Blend modes — screen, multiply, overlay, color-dodge |
| ⚡ | Progressive loading — animation starts immediately |
| ♿ | Reduced motion — all exports respect prefers-reduced-motion |
| 📦 | 4 export formats — HTML · Vanilla JS · React/Next.js · Vue 3 |
Install as a Node module
npm install scroll-cinemaconst { applyEasing, scrollToFrame, totalScrollHeight } = require('scroll-cinema')
// Map scroll position to frame index
const frame = scrollToFrame(window.scrollY, 120, 24, 'spring')
// Get total wrapper height for CSS
const height = totalScrollHeight(120, 24) // → 3080px
// Apply easing manually
const progress = applyEasing(0.5, 'spring') // → 0.938...Exported code examples
HTML snippet
<div class="sc-wrap">
<div class="sc-sticky"><canvas id="sc"></canvas></div>
</div>React / Next.js
import ScrollCinema from './ScrollCinema'
<ScrollCinema
frameCount={120}
basePath="/frames/"
scrollPerFrame={24}
easing="spring"
onProgress={({ progress }) => console.log(progress)}
/>Vue 3 / Nuxt
<ScrollCinema :frame-count="120" base-path="/frames/" easing="spring" />Apple does this
Visit apple.com/iphone in Chrome DevTools — you'll find a sticky canvas, ~140 PNG frames, and a scroll event mapping position to frame index. ScrollCinema generates that exact pattern in minutes.
Extracting frames with FFmpeg
# 24fps, 1280px wide, JPEG
ffmpeg -i input.mp4 -vf fps=24,scale=1280:-1 -q:v 3 frames/frame%03d.jpg
# Limit to 120 frames
ffmpeg -i input.mp4 -vf fps=24,scale=1280:-1 -frames:v 120 frames/frame%03d.jpgNo FFmpeg? Drop your video directly into ScrollCinema — it extracts in the browser.
API Reference
applyEasing(t, type)
| Param | Type | Description |
|---|---|---|
| t | number | Progress value 0–1 |
| type | string | Easing name: linear, ease-in, ease-out, ease-in-out, expo, spring, elastic, bounce, stepped |
scrollToFrame(scrollY, frameCount, scrollPerFrame, easing)
Returns the frame index (0-based) for a given scroll position.
totalScrollHeight(frameCount, scrollPerFrame)
Returns the CSS height in pixels for the scroll wrapper div.
License
MIT — Copyright (c) 2026 vvlars-cmd
Validity: 2026-03-27 to 2026-09-27
