Loading...
Loading...
Motion Canvas framework reference covering project setup, core concepts (generators, signals, refs, scene hierarchy, timing, utilities), and 2D components (shapes, paths, text, media, layout, camera, transitions, custom components). Use when building or editing Motion Canvas scenes.
npx skill4agent add videozero/skills motion-canvasimport {makeScene2D} from '@motion-canvas/2d';
export default makeScene2D(function* (view) {
});function*yieldyield*export default makeScene2D(function* (view) {
const circle = createRef<Circle>();
view.add(<Circle ref={circle} width={100} height={100} fill={'red'} />);
yield* circle().position.x(300, 1);
yield* circle().position.x(-300, 1);
});function* flicker(circle: Circle, duration: number): ThreadGenerator {
const colors = ['#e13238', '#e6a700', '#99C47A'];
for (const color of colors) {
circle.fill(color);
yield* waitFor(duration);
}
}
yield* flicker(myCircle(), 0.5);import {createSignal} from '@motion-canvas/core';
const radius = createSignal(3);
radius(); // Get → 3
radius(5); // Set → 5
yield* radius(4, 2); // Tween to 4 over 2 secondsconst area = createSignal(() => Math.PI * radius() * radius());<Circle width={() => radius() * 2} height={() => radius() * 2} />
yield* radius(200, 1); // Circle updates automaticallyconst position = Vector2.createSignal(Vector2.up);
yield* position(Vector2.zero, 1);import {DEFAULT} from '@motion-canvas/core';
signal(DEFAULT); // Instant reset
yield* signal(DEFAULT, 2); // Tween to defaultconst circle = createRef<Circle>();
<Circle ref={circle} width={100} height={100} fill={'red'} />
yield* circle().scale(2, 0.3);const circles: Circle[] = [];
{range(10).map(index => (
<Circle ref={makeRef(circles, index)} x={index * 50} width={40} height={40} />
))}
yield* all(...circles.map(c => c.scale(1.5, 0.5)));const labels = createRefMap<Txt>();
<Txt ref={labels.a} text="Label A" />
<Txt ref={labels.b} text="Label B" />
yield* labels.a().text('Updated A', 0.3);view.add(<Circle />); // Add to view
container().add(<Circle />); // Add to node
container().insert(<Circle />, 0); // Insert at index
circle().remove(); // Remove
container().removeChildren(); // Remove all children
circle().reparent(newParent()); // Move to new parentmoveUp()moveDown()moveToTop()moveToBottom()moveTo(2)import {is} from '@motion-canvas/2d';
const textNodes = view.findAll(is(Txt));
const firstCircle = view.findFirst(is(Circle));yield* circle().save();
yield* all(circle().position.x(300, 1), circle().scale(2, 1));
yield* circle().restore(1); // Animate back to saved stateimport {waitFor, waitUntil, useDuration} from '@motion-canvas/core';
yield* waitFor(2); // Wait 2 seconds
yield* waitUntil('voice-line-2'); // Wait for named event
const dur = useDuration('segment'); // Get event durationuseRandom(42).nextInt(10, 100).nextFloat(0, 1)useLogger().debug().info().warn().error()debug('msg')useScene().getSize()useTime()range(5)[0,1,2,3,4]range(2,5)[2,3,4]// Spawn a background thread (do NOT yield — spawn starts it automatically)
const task = spawn(function* () {
yield* loop(Infinity, function* () {
yield* circle().rotation(360, 2);
circle().rotation(0);
});
});
// Cancel a running thread
cancel(task);
// Wait for a thread to finish
yield* join(task);yield a(); // run a without waiting for a
yield* waitFor(0.5); // wait 0.5s
yield* b(1); // run b<Circle width={200} height={200} fill={'#e13238'} stroke={'#fff'} lineWidth={4}
startAngle={0} endAngle={270} closed={false} /><Rect width={300} height={200} fill={'#68ABDF'} radius={10} smoothCorners cornerSharpness={0.6} /><Line points={[[0,0],[100,100],[200,50]]} stroke={'#fff'} lineWidth={4}
lineDash={[20,10]} lineCap={'round'} lineJoin={'round'} startArrow endArrow arrowSize={12} /><Polygon sides={6} size={200} fill={'#99C47A'} />import {Grid} from '@motion-canvas/2d';
<Grid width={'100%'} height={'100%'} stroke={'#333'} lineWidth={1} spacing={80} start={0} end={1} />startendimport {Path} from '@motion-canvas/2d';
<Path data={'M 0 -100 L 29 -40 L 95 -31 Z'} stroke={'#e6a700'} lineWidth={3} />yield* path().data(newPathData, 1);import {blur, brightness, grayscale, sepia, contrast, saturate, hue, invert} from '@motion-canvas/2d';
<Rect filters={[blur(5), brightness(1.5)]} />
yield* rect().filters([blur(0), grayscale(1)], 1); // Animatedimport {Gradient} from '@motion-canvas/2d';
const grad = new Gradient({
type: 'linear',
from: [-100, 0], to: [100, 0],
stops: [{offset: 0, color: '#e13238'}, {offset: 1, color: '#68ABDF'}],
});
<Rect fill={grad} /><Ray from={[0,0]} to={[300,200]} endArrow />start(1,1)end(0,1)<CubicBezier p0={..} p1={..} p2={..} p3={..} /><QuadBezier p0={..} p1={..} p2={..} /><Spline points={[..]} />new Knot([x,y], sharpness)<Txt text={'Hello World'} fontSize={64} fontFamily={'Inter'} fill={'#ffffff'} wrap={true} />export class Switch extends Node {
@initial(false) @signal()
public declare readonly initialState: SimpleSignal<boolean, this>;
public constructor(props?: SwitchProps) {
super({...props});
}
public *toggle(duration: number) { /* animation logic */ }
}@motion-canvas/2d@signal()@initial(value)@colorSignal()@vector2Signal()import {Node, NodeProps, initial, signal} from '@motion-canvas/2d';import {slideTransition, fadeTransition, Direction} from '@motion-canvas/core';
yield* slideTransition(Direction.Left);@motion-canvas/coreslideTransition(Direction.Left)fadeTransition(duration?)zoomInTransition(area, duration?)zoomOutTransition(area, duration?)waitTransition(duration?)import {useTransition} from '@motion-canvas/core';
const transition = useTransition(ctx => { /* current */ }, ctx => { /* previous */ });
yield* transition(1);if (cond()) yield* a(); else yield* b();<Circle fill={() => val() > 150 ? 'red' : 'blue'} />while/switch