What this is for
- A short film that argues one thing about a product. Anything past 60 seconds needs a reason to exist
- Every frame is React, so scenes are reviewed, diffed and changed like any other component. That is the whole point of doing it in code rather than in an editor
- The tool is Remotion. A composition declares width, height, fps and duration in frames, and a component renders whatever belongs at the current frame
The shape of a composition
- One file holds the running order. Every scene is an entry with an id, a node and a length in frames
- Keep that file readable on its own. If someone cannot follow the story from the scene ids alone, the video will not land either
Seriesplays scenes back to back without you tracking absolute offsets. EachSeries.SequencetakesdurationInFramesAbsoluteFillis the full-frame layer. Stack them for background, content and overlaysuseCurrentFrame()is the clock. There is norequestAnimationFrameand no wall time: the frame number is the only input, which is what makes a render deterministic
Movement
interpolate(frame, [inFrame, outFrame], [from, to], { extrapolateLeft: "clamp", extrapolateRight: "clamp" })covers most moves. Always clamp, or values keep travelling outside the rangespring({ frame, fps, config: { damping } })gives weight. Higher damping settles faster with less overshoot. Around 26 reads as confident, around 12 reads as bouncy- Animate a handful of properties: position, opacity, scale. Everything else is decoration
- Nothing should still be moving when a scene ends. Land the motion, hold the frame, then cut
Assets
- Reference files through
staticFile("logos/thing.svg"), never a bare path, or the render cannot resolve them - Fonts must be loaded and waited for. A webfont that arrives late changes every text measurement in the frame
Rendering
- Render at 1920x1080 for the master, then re-encode for the web. A raw Remotion H.264 render of 55 seconds came out at 11 MB
ffmpeg -i master.mp4 -vf scale=1280:-2 -c:v libx264 -crf 26 -preset slow -movflags +faststart -c:a aac -b:a 96k web.mp4took that same file to 2.3 MB with no visible loss at page size+faststartmatters. Without it the player waits for the whole file before it can begin- Pull the poster from a real frame of the finished render, not from a mockup, so the still and the first frames agree
- The first install pulls a headless Chrome. Keep the video project out of your app's workspace so it never lands in a teammate's install or in CI