Keyframe animations
Scale, position, blur, and rotation animated with custom easings.
// 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 $;