Remotion keyframes
Skill phamthanhnghia/remotion-agent-skills/skills/remotion-keyframes
Programmatic video creation agent skills suite for Remotion, modeled on HyperFrames architecture. Build explainers, product launches, motion graphics, captions, and data viz with AI.
npx -y skills add phamthanhnghia/remotion-agent-skills --skill remotion-keyframesAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
3 things to look at
- 15 days oldThe repository was created 15 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Keyframe animation engine for Remotion. Use for precise frame-level animation, interpolate curves, stagger patterns, entrance/exit choreography, and text reveal animations.
SKILL.md
7.0 KB, ~1.9k tokens by cl100k_base, as published. Nobody here has run it
Remotion Keyframes
Keyframes are a pose contract: visible states, continuous subject identity, frame-deterministic, verified pixels.
Use remotion-animation for broad scene recipes. Use remotion-cli for full command docs.
Procedure
- Identify the animated subject, visible states, final state, and animation primitive (
springorinterpolate). - Choose the smallest mechanism that proves the prompt.
- Author frame-deterministic keyframes. All values derive from
useCurrentFrame(). - Verify with
npx remotion studio— scrub the full timeline, check first frame, all poses, and final frame. - If proof fails, fix the source keyframes and re-verify before rendering.
Contract
- Name the moving subject.
- Name the poses needed to prove the intended motion, including the final state.
- Keyframe visible channels, not hidden helper state.
- Preserve object identity when continuity matters.
- Hold readable or semantic states long enough to see (minimum 10 frames for text, 6 frames for icon).
- Final frame is part of the animation, not cleanup.
- Do not reset to rest unless requested.
- Do not end on black unless requested.
Runtime Rules
interpolate() and spring():
- all values computed synchronously from
frame— no async inside interpolate/spring calls extrapolateLeft: 'clamp'andextrapolateRight: 'clamp'to prevent value leaking outside intended rangespring({frame, fps, config})— frame must be the RemotionuseCurrentFrame()value, not a derivedDate.now()derivative- For entrances, offset spring:
spring({frame: frame - delay, fps, config}); useMath.max(0, frame - delay)to avoid negative frames driving spring
Never use for render-critical motion:
Date.now()orperformance.now()- unseeded
Math.random()(use Remotion'srandom(seed)instead) - CSS transitions or CSS animations (they don't seek)
requestAnimationFramesetTimeout/setIntervalin render pathuseState/useEffectfor visual-property values
Spring Config Reference
import {spring, useCurrentFrame, useVideoConfig} from 'remotion';
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
// Snappy entrance (default feel)
const scale = spring({frame, fps, config: {damping: 200}});
// Bouncy entrance
const bounce = spring({frame, fps, config: {damping: 8, stiffness: 300}});
// Very smooth, slow
const smooth = spring({frame, fps, config: {damping: 30, stiffness: 80, mass: 2}});
// Sharp exit (reverse spring — animate from 1 to 0)
const {durationInFrames} = useVideoConfig();
const exitFrame = durationInFrames - frame;
const exitScale = spring({frame: exitFrame, fps, config: {damping: 200}});
const scaleValue = 1 - (1 - exitScale) ; // goes from 1 down to 0
Interpolate Recipes
import {interpolate} from 'remotion';
// Fade in over 20 frames
const opacity = interpolate(frame, [0, 20], [0, 1], {extrapolateRight: 'clamp'});
// Slide in from left
const x = interpolate(frame, [0, 30], [-300, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// Multi-stop color shift
const r = interpolate(frame, [0, 30, 60], [255, 100, 0], {extrapolateRight: 'clamp'});
const g = interpolate(frame, [0, 30, 60], [0, 150, 255], {extrapolateRight: 'clamp'});
// Scale pop
const scale = interpolate(frame, [0, 8, 12, 18], [0, 1.15, 0.95, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
Channels
Prefer compositor/visual channels: transform: translateX/Y/Z, transform: scale/rotate/skew, opacity, borderRadius, CSS vars, clipPath, SVG strokeDashoffset.
Avoid layout channels: top/left/right/bottom, width/height, margin/padding — they trigger reflow, are slow, and produce incorrect results when the renderer samples frames in parallel.
Stagger Pattern
const ITEM_COUNT = 5;
const STAGGER_FRAMES = 8; // delay between each item
const items = Array.from({length: ITEM_COUNT}, (_, i) => {
const delayedFrame = Math.max(0, frame - i * STAGGER_FRAMES);
const itemSpring = spring({frame: delayedFrame, fps, config: {damping: 200}});
return {opacity: itemSpring, y: (1 - itemSpring) * 40};
});
Text Animation Pattern
const words = 'Hello Remotion World'.split(' ');
const WordReveal = () => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
return (
<div style={{display: 'flex', gap: 12}}>
{words.map((word, i) => {
const delayedFrame = Math.max(0, frame - i * 6);
const progress = spring({frame: delayedFrame, fps, config: {damping: 200}});
return (
<span
key={i}
style={{
opacity: progress,
transform: `translateY(${(1 - progress) * 20}px)`,
display: 'inline-block',
}}
>
{word}
</span>
);
})}
</div>
);
};
SVG Stroke Draw
// SVG stroke-dasharray draw-on effect
const pathLength = 500; // measure with path.getTotalLength() in development
const drawProgress = interpolate(frame, [0, 60], [0, 1], {extrapolateRight: 'clamp'});
<path
d="M 0 0 L 300 200"
stroke="white"
strokeWidth={4}
fill="none"
strokeDasharray={pathLength}
strokeDashoffset={pathLength * (1 - drawProgress)}
/>
3D Depth (CSS Perspective)
// CSS perspective 3D scene
<div style={{perspective: 800, perspectiveOrigin: '50% 50%'}}>
<div
style={{
transformStyle: 'preserve-3d',
transform: `rotateY(${interpolate(frame, [0, 60], [45, 0], {extrapolateRight: 'clamp'})}deg)`,
}}
>
{/* 3D content */}
</div>
</div>
Mechanism Choice
| Need | Mechanism |
|---|---|
| Entrance with natural deceleration | spring() |
| Timed value transition | interpolate() |
| Multi-stop sequence | interpolate() with multiple input/output stops |
| Many items with ordered entrance | stagger: indexed frame - i * delay in spring() |
| Text reveal (word by word) | stagger spring per word index |
| Stroke grows or traces | strokeDashoffset via interpolate() |
| Shape reveal boundary | clipPath via interpolate() |
| 3D depth scene | CSS perspective + rotateX/Y/Z via interpolate() |
Error Handling
| Failure | Fix |
|---|---|
| Values jump at certain frames | Check for negative frame passed to spring() — clamp with Math.max(0, frame - delay) |
| Animation doesn't seek correctly | Confirm no useState/CSS animation driving the value |
| Black frames at start or end | Check extrapolateLeft: 'clamp' on entrance, extrapolateRight: 'clamp' on exit |
| Text blinking / inconsistent | Ensure random() seed is a stable string, not Math.random() |
| Spring overshoots off-screen | Increase damping (200 = no overshoot, 8 = lots of bounce) |
Done
Verify in Studio: scrub first frame, all visible poses, final frame. Confirm subject-owned motion, no debug overlays, no flash on first frame.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.