R RenderComp
remotion interpolate animation tutorial react

Remotion interpolate(): The Complete Guide to Value Mapping

Remotion interpolate(): The Complete Guide to Value Mapping

Animation in Remotion is, at its heart, a question of mapping. At frame 0, a title should be invisible. At frame 20, it should be fully visible. At frame 30, a card should sit at its resting position. At frame 60, it should have slid in from off-screen. Every animated value in every frame is just a number — and interpolate() is the function that connects those numbers together.

If you have used React Native’s Animated API, the concept will feel familiar. If you are coming from CSS animations or After Effects, interpolate() is the equivalent of a keyframe graph editor baked into a single function call. Once you internalize it, you will find yourself reaching for it constantly.

This guide covers the full interpolate() API, its options, easing integration, real-world patterns, and the mistakes that trip up nearly every developer who is new to Remotion.


What interpolate() Does

interpolate() maps a value from one numeric range to another. That is the entire job. You give it:

  1. An input value — almost always the current frame number
  2. An input range — the range of frame numbers you care about
  3. An output range — the values you want the input to map to

The function performs a linear interpolation between the output values, proportional to where the input falls within its range.

The simplest mental model: imagine a ruler. The input range defines two marks on the ruler (say, frames 0 and 30). The output range defines what value each mark represents (say, 0 and 1 for opacity). When the input is at frame 15 — exactly halfway — interpolate() returns 0.5.

This deceptively simple mechanism is the foundation of virtually every animation in Remotion.


Basic Signature

import { interpolate } from 'remotion';

interpolate(
  value,        // number — the input value, usually the current frame
  inputRange,   // number[] — e.g. [0, 30]
  outputRange,  // number[] — e.g. [0, 1]
  options?      // ExtrapolateOptions
)

Both inputRange and outputRange must have the same number of elements. The input range must be monotonically increasing (each value must be greater than or equal to the previous one).

Here is the canonical fade-in:

import { interpolate, useCurrentFrame } from 'remotion';

export const FadeIn: React.FC = () => {
  const frame = useCurrentFrame();

  const opacity = interpolate(frame, [0, 20], [0, 1], {
    extrapolateRight: 'clamp',
  });

  return (
    <div style={{ opacity }}>
      Hello, Remotion
    </div>
  );
};

At frame 0, opacity is 0. At frame 10, it is 0.5. At frame 20, it is 1. After frame 20, it stays clamped at 1 — which is exactly what you want.


The extrapolateLeft and extrapolateRight Options

By default, interpolate() extends the linear trend beyond the boundaries of your input range. This is called 'extend' extrapolation, and it is the default. If your input range is [0, 20] and the current frame is 40, the function will return a value that linearly extends the slope beyond your output range — which in the case of opacity would give you 2, a completely invalid CSS value.

This is why the extrapolateRight: 'clamp' option is ubiquitous in Remotion code. It tells the function to stop increasing once the input passes the right boundary of the range.

Both options accept the same set of values:

ValueBehaviour
'extend'Linearly continues the slope past the range boundary (default)
'clamp'Locks the output at the boundary value once the input exceeds the range
'wrap'Wraps the input back to the start of the range (useful for loops)
'identity'Returns the input value unchanged outside the range

In practice, 'clamp' is what you want 95% of the time. Here is a complete example showing both sides:

const opacity = interpolate(frame, [10, 30], [0, 1], {
  extrapolateLeft: 'clamp',   // before frame 10 → opacity stays 0
  extrapolateRight: 'clamp',  // after frame 30 → opacity stays 1
});

When you omit extrapolateLeft, the default 'extend' can produce negative opacity values for frames before 10 — a silent bug that is hard to diagnose visually.


Easing Functions: Adding Character to Linear Motion

Linear interpolation is correct, but it rarely looks good. A title that appears at a perfectly uniform rate feels mechanical. Real-world motion accelerates and decelerates.

Remotion ships an Easing utility (re-exported from React Native) that provides a library of standard easing functions. You pass one as the easing option inside the options object:

import { interpolate, Easing, useCurrentFrame } from 'remotion';

const frame = useCurrentFrame();

const opacity = interpolate(frame, [0, 30], [0, 1], {
  extrapolateRight: 'clamp',
  easing: Easing.ease,
});

The easing function operates on the normalized input before it is mapped to the output range. In other words, interpolate() first converts your frame number to a 0–1 progress value, passes that through the easing function, then maps the result to your output range.

Commonly Used Easing Functions

// Smooth ease in and out — the most versatile default
easing: Easing.ease

// Smooth with more dramatic acceleration and deceleration
easing: Easing.inOut(Easing.cubic)

// Fast start, slow end — like a ball rolling to a stop
easing: Easing.out(Easing.quad)

// Slow start, fast end — like something gaining momentum
easing: Easing.in(Easing.cubic)

// Cubic bezier — full manual control (equivalent to CSS cubic-bezier)
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0)

// Elastic overshoot
easing: Easing.elastic(1)

// Bounce on arrival
easing: Easing.bounce

A note on Easing.elastic and Easing.bounce: these functions can return values outside the 0–1 range, which is intentional — the overshoot is the effect. If you use clamp on the output side, you will cut off the bounce, defeating its purpose. Be intentional about when you clamp.


Common Animation Patterns

Fade In

const opacity = interpolate(frame, [0, 20], [0, 1], {
  extrapolateLeft: 'clamp',
  extrapolateRight: 'clamp',
});

Fade Out

const opacity = interpolate(frame, [40, 60], [1, 0], {
  extrapolateLeft: 'clamp',
  extrapolateRight: 'clamp',
});

Slide In from Left

const translateX = interpolate(frame, [0, 25], [-200, 0], {
  extrapolateLeft: 'clamp',
  extrapolateRight: 'clamp',
  easing: Easing.out(Easing.cubic),
});

// Apply in JSX:
// style={{ transform: `translateX(${translateX}px)` }}

Scale Up (Pop In)

const scale = interpolate(frame, [0, 20], [0.5, 1], {
  extrapolateLeft: 'clamp',
  extrapolateRight: 'clamp',
  easing: Easing.out(Easing.back(1.5)),
});

// style={{ transform: `scale(${scale})` }}

Easing.back() produces a slight overshoot past 1 before settling, giving the element a satisfying “snap into place” feel.

Rotate In

const rotate = interpolate(frame, [0, 30], [-15, 0], {
  extrapolateLeft: 'clamp',
  extrapolateRight: 'clamp',
  easing: Easing.out(Easing.quad),
});

// style={{ transform: `rotate(${rotate}deg)` }}

Chaining Animations with Multiple interpolate Calls

Real animations almost always involve multiple properties animated simultaneously, and often with different timings. The pattern is to call interpolate() once per property, each with its own input range:

export const AnimatedCard: React.FC = () => {
  const frame = useCurrentFrame();

  const opacity   = interpolate(frame, [0, 15], [0, 1], { extrapolateRight: 'clamp' });
  const translateY = interpolate(frame, [0, 20], [40, 0], {
    extrapolateRight: 'clamp',
    easing: Easing.out(Easing.cubic),
  });
  const scale     = interpolate(frame, [5, 25], [0.9, 1], { extrapolateRight: 'clamp' });

  return (
    <div
      style={{
        opacity,
        transform: `translateY(${translateY}px) scale(${scale})`,
      }}
    >
      Card content
    </div>
  );
};

The three properties are animated with slightly staggered ranges, creating a layered entrance that feels richer than any single-property tween.

You can also use interpolate() to drive a property through multiple keyframe-like stages in a single call by passing arrays with more than two elements:

// Three-stage opacity: fade in, hold, fade out
const opacity = interpolate(
  frame,
  [0,  20, 60, 80],
  [0,   1,  1,  0],
  { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);

This single call replaces what would otherwise require three separate calculations and conditional logic.


Using interpolate with useCurrentFrame

interpolate() is a pure function — it has no side effects and takes no special context. useCurrentFrame() is the hook that provides the current frame number within a Remotion composition. Together, they form the backbone of frame-based animation.

import { interpolate, useCurrentFrame, useVideoConfig } from 'remotion';

export const ProgressBar: React.FC = () => {
  const frame = useCurrentFrame();
  const { durationInFrames } = useVideoConfig();

  // Animate from 0% to 100% over the full duration of the composition
  const progress = interpolate(frame, [0, durationInFrames], [0, 100], {
    extrapolateRight: 'clamp',
  });

  return (
    <div style={{ width: '100%', height: 8, background: '#eee', borderRadius: 4 }}>
      <div
        style={{
          width: `${progress}%`,
          height: '100%',
          background: '#0066ff',
          borderRadius: 4,
        }}
      />
    </div>
  );
};

useVideoConfig() exposes durationInFrames, fps, width, and height — the composition’s global configuration. Using durationInFrames as the upper bound of your input range makes an animation span the entire clip, regardless of how long the composition is.

One important caveat: useCurrentFrame() must be called at the top level of a React component — not inside a callback, loop, or conditional. This is a standard React hooks rule. interpolate() itself, however, is a plain function and can be called anywhere.


Common Mistakes

1. Forgetting to Clamp

The most frequent bug. You write interpolate(frame, [0, 30], [0, 1]) and assume the output is always between 0 and 1. It is not — after frame 30, the value keeps climbing. Always add extrapolateRight: 'clamp' unless you explicitly want the extrapolation behaviour.

2. Reversing the Input Range Order

The input range must be monotonically increasing. Writing [30, 0] instead of [0, 30] is not how you reverse an animation — it will throw an error or produce undefined behaviour. To reverse an animation, reverse the output range instead:

// Correct: fade OUT from frame 0 to 30
const opacity = interpolate(frame, [0, 30], [1, 0], { extrapolateRight: 'clamp' });

3. Mismatched Array Lengths

inputRange and outputRange must always have the same number of elements. Passing [0, 15, 30] as the input range and [0, 1] as the output range will throw at runtime.

4. Applying Easing and Expecting It to Work Without Clamping

Easing functions like Easing.elastic and Easing.bounce produce values outside 0–1. If downstream CSS properties require values within a specific range, you need to account for this — either by accepting the out-of-range values (which is fine for transforms) or by not clamping and letting the overshoot be visible.

5. Using interpolate Outside a Composition

interpolate() itself works anywhere, but useCurrentFrame() only works inside a Remotion composition tree. If you call it outside of a <Composition>, React will throw a hook-rules error. This matters when you extract animation logic into utility files.

6. Overlooking Multi-Stop Interpolation

Many developers use multiple interpolate() calls with if/else logic to manage complex multi-stage animations. The multi-stop syntax — passing arrays longer than two elements — is cleaner and less error-prone. Use it.


FAQ

Q: Can interpolate() work with values other than numbers? For color interpolation, Remotion provides a separate interpolateColors() function. For plain numeric CSS values (opacity, pixel positions, degrees, scale factors) the standard interpolate() function covers everything. String values like 'auto' are not supported — convert them to numbers first.

Q: What is the difference between interpolate() and spring()? interpolate() maps frame numbers to values deterministically — you define exactly what happens at exactly what frame. spring() runs a physics simulation, so the duration emerges from the physics parameters rather than being explicitly set. Use interpolate() when you need precise timing; use spring() when you want a natural, physical feel with overshoot.

Q: Can I pass something other than the current frame as the input value? Absolutely. interpolate() takes any number. You might use a scroll offset, a percentage, or even the output of another interpolate() call as the input. The function does not know or care where the number came from.

Q: Why does my animation flicker on the first or last frame? This almost always traces back to missing extrapolateLeft: 'clamp'. On frame 0, if your input range starts at frame 5, the default 'extend' extrapolation will return a negative value for your output — which can cause a visual artifact on that frame.

Q: How many stops can I use in a multi-stop interpolate()? There is no documented hard limit. In practice, more than 6–8 stops in a single call becomes difficult to read and maintain. At that point, consider splitting the animation into distinct <Sequence> segments within your composition.

Q: Does interpolate() re-render on every frame? Remotion renders each frame as a React component snapshot. useCurrentFrame() returns a different number for each frame, so yes — the component re-evaluates for every frame. This is expected and by design. interpolate() is extremely fast (pure arithmetic), so this is never a performance concern.

Q: Can I use interpolate() inside a Remotion template from RenderComp? Yes. All RenderComp templates are standard Remotion compositions written in React. You can call interpolate() anywhere inside a template component, and you can pass custom frame ranges as props to create re-usable animated building blocks.


Wrapping Up

interpolate() is the single most important function in Remotion’s API surface. Everything else — spring physics, sequences, audio volume, text reveal — eventually connects back to mapping numbers to values across time. Once you are fluent in interpolate(), reading and writing Remotion animations becomes intuitive.

The key habits to build:

  • Always clamp unless you have a specific reason not to
  • Use multi-stop arrays instead of conditional logic for complex timelines
  • Layer multiple interpolate() calls with staggered ranges to create rich, multi-property entrances
  • Pass easing functions to add physical character without switching to spring physics
  • Use useVideoConfig() to make your ranges relative to composition length, not hard-coded frame counts

If you are looking for production-ready components that demonstrate these patterns at scale, RenderComp provides a library of Remotion animation templates you can drop directly into your project — each one built on exactly the principles covered in this guide.


Published by RenderComp — Remotion animation templates for production video.

Now available

Get 1,000+ Remotion Templates

Pay once — no subscription. Lifetime updates. TypeScript-first.

View pricing →