VideoFlowcodeGitHubTry itCoreRenderersReact Video EditorPlaygroundExamplesDocscodeGitHubTry it
Example

Keyframe animations

Scale, position, blur, and rotation animated with custom easings.

#animation
// Two beats showing the full keyframe API: hand-rolled animate() calls,
// transition presets, and removal — animating scale, position, blur, rotation.
const $ = new VideoFlow({
  name: 'Keyframe Animations',
  width: 1920,
  height: 1080,
  fps: 30,
  backgroundColor: '#040411',
});

// Beat 1 — `wait: false` lets the ring flip behind the title's own entrance.
const ring = $.addShape(
  {
    width: 80, height: 25,
    fill: '#ff5a1f20', strokeColor: '#ff5a1f', strokeWidth: 0.35,
    cornerRadius: 1,
    position: [0.5, 0.40], opacity: 0,
  },
  { shapeType: 'rectangle' },
);
ring.animate(
  { opacity: 0, scale: 0.3, rotation: 0 },
  { opacity: 1, scale: 1.0, rotation: 180 },
  { duration: '900ms', easing: 'easeOut', wait: false },
);

// Title — declarative entry via the `zoom` transition preset.
const title = $.addText(
  {
    text: 'Animate',
    fontSize: 7.5, fontWeight: 800, color: '#ffffff',
    position: [0.5, 0.40], letterSpacing: -0.04,
  },
  { transitionIn: { transition: 'zoom', duration: '600ms', params: { from: 0.85 } } },
);
$.wait('600ms');

// Subtitle's transitionOut (declared here) fires automatically on remove().
const sub = $.addText(
  {
    text: 'Scale  ·  Position  ·  Rotation  ·  Blur  ·  etc.',
    fontSize: 4.0, fontWeight: 400, color: '#6b7280', position: [0.5, 0.72],
  },
  {
    transitionIn:  { transition: 'slideUp', duration: '500ms', params: { distance: 0.05 } },
    transitionOut: { transition: 'fade', duration: '400ms' },
  },
);
$.wait('2s');

// Custom exit for title + ring; subtitle uses its declared transitionOut.
title.animate(
  { scale: 1.0, opacity: 1 },
  { scale: 2.2, opacity: 0 },
  { duration: '600ms', easing: 'easeIn', wait: false },
);
ring.animate(
  { scale: 1.0, opacity: 1 },
  { scale: 0.0, opacity: 0 },
  { duration: '500ms', easing: 'easeIn', wait: false },
);
sub.remove();
$.wait('600ms');
title.remove();
ring.remove();

// Beat 2 — one word per animatable property family. filterBlur in em.
const blurWord = $.addText(
  {
    text: 'Blur', fontSize: 4.5, fontWeight: 700,
    color: '#4ecdc4', position: [0.5, 0.3], filterBlur: 1.5, opacity: 0,
  },
  { transitionOut: { transition: 'fade', duration: '500ms' } },
);
blurWord.animate(
  { filterBlur: 1.5, opacity: 0 },
  { filterBlur: 0,   opacity: 1 },
  { duration: '700ms', easing: 'easeOut' },
);
$.wait('300ms');

// position is animated as an [x, y] vector — interpolated component-wise.
const posWord = $.addText(
  {
    text: 'Position', fontSize: 4.5, fontWeight: 700,
    color: '#ffd166', position: [0.15, 0.52], opacity: 0,
  },
  { transitionOut: { transition: 'fade', duration: '500ms' } },
);
posWord.animate(
  { opacity: 0, position: [0.15, 0.52] },
  { opacity: 1, position: [0.50, 0.52] },
  { duration: '700ms', easing: 'easeOut' },
);
$.wait('300ms');

// Several scalar props animated together in one animate() call.
const scaleWord = $.addText(
  {
    text: 'Scale', fontSize: 4.5, fontWeight: 700,
    color: '#ff5a1f', position: [0.5, 0.73], opacity: 0, scale: 0.2, rotation: -30,
  },
  { transitionOut: { transition: 'fade', duration: '500ms' } },
);
scaleWord.animate(
  { opacity: 0, scale: 0.2, rotation: -30 },
  { opacity: 1, scale: 1.0, rotation:   0 },
  { duration: '700ms', easing: 'easeOut' },
);
$.wait('2s');

// .remove() triggers each layer's declared transitionOut.
blurWord.remove();
posWord.remove();
scaleWord.remove();
$.wait('500ms');

return $;
Compiling00:00
play_arrowOpen in playgroundcodeSource on GitHub← All examples
VideoFlow

Open-source toolkit for composing videos from code.

Product

CoreRenderersReact Video EditorPlayground

Learn

DocsAPI referenceExamplesvs. Remotionvs. FFmpeg

Project

GitHubLicenseContact

Legal

TermsPrivacy
© 2026 VideoFlow. Apache-2.0 core.