Skip to content
Apexify.jsDocs
Apexify.js version v5.4.5

Node / Gif Animation · guide

animate — declarative frame sequences

Current Apexify.js 6.0.0 documentation for animate — declarative frame sequences.

apexify.jsRuntime: nodeCURRENTSince 6.0.0

await painter.animate(frames, defaultDuration, defaultWidth?, defaultHeight?, options?, painterOpts?) renders declarative Frame definitions. It either returns PNG Buffer[] or writes a GIF file through @skyra/gifenc.

This helper is separate from createGIF: it does not expose per-frame GIF disposal/transparency, GIFWatermarkSpec, onStart frame producers, attachment/base64 outputs, or GIF encoder tuning. Use createGIF for the full GIF-specific API.

Implementation: lib-next/gif/animate-frames.ts. Frame lives in types/gif.ts.


Signature

SOURCEtypescript
typescript
Studio
animate(  frames: Frame[],  defaultDuration: number,  defaultWidth?: number,   // default 800  defaultHeight?: number,  // default 600  options?: AnimateOptions,  painterOpts?: PainterAssetRefsOptions): Promise<Buffer[] | undefined>;

frames must be non-empty and stay within central collection/GIF resource limits. Durations are milliseconds and may be zero.

Optional painterOpts.resolveAssetRefs resolves supported $… asset references before animation rendering.


AnimateOptions

SOURCEtypescript
typescript
Studio
type AnimateOptions = {  gif?: boolean;  gifPath?: string;  onStart?: () => void;  onFrame?: (index: number) => void;  onEnd?: () => void;  signal?: AbortSignal;};

When gif: true, gifPath is required.

Hook order

  • onStart fires after validation/cancellation checks and before frame iteration.
  • onFrame(i) fires after that frame has been drawn and, in GIF mode, handed to the encoder.
  • onEnd fires only after the loop. In GIF mode it runs after encoder.finish(), destination-stream completion, and GIF signature validation.

An already-aborted signal prevents onStart and output allocation. Failures after GIF output begins destroy the stream and remove the partial file.


Return value and timing behavior

options.gifReturn / pacing
false / omittedBuffer[], one PNG per frame. After each frame, animate waits frame.duration ?? defaultDuration ms when the value is positive.
trueundefined. Frames are encoded directly to gifPath; the helper does not sleep between encoder submissions. The GIF frame delay still uses frame.duration ?? defaultDuration.

GIF mode resolves only after the output file is complete and its header is verified as GIF87a/GIF89a.

The fixed encoder settings for this convenience path are repeat 0 (loop indefinitely) and quality 10.


GIF dimensions are fixed

A GIF encoder has one logical screen size, so GIF mode uses exactly defaultWidth × defaultHeight for every frame.

A frame-level width/height override that would differ from those GIF dimensions is rejected. This prevents silent cropping or stale pixels from a previous frame.

Frame-level dimensions remain supported when gif is false, where each PNG frame has its own canvas.


Rendering order

For each frame:

  1. choose frame.width/height or the defaults (non-GIF mode); GIF mode has already required the fixed output size
  2. clear the canvas
  3. apply optional transform (translate → rotate → scale)
  4. resolve background paint:
    • gradient through Apexify's shared gradient renderer (linear, radial, and conic)
    • pattern, when supplied, replaces the gradient fill
    • otherwise backgroundColor
  5. draw optional source with blendMode (default source-over)
  6. invoke onDrawCustom(ctx, canvas)
  7. restore the transform
  8. encode the GIF frame or snapshot PNG bytes
  9. invoke onFrame
  10. non-GIF mode only: apply the requested wall-clock delay

GradientConfig.angle remains supported as the legacy conic-angle alias when startAngle is not provided.


Media sources use central policy

Frame.source and Frame.pattern.source are strings. Paths, data-image URLs, and allowed remote URLs are resolved through Apexify's shared media layer.

Remote animation sources therefore use the same protocol/host trust policy, SSRF protections, redirect/timeout rules, remote-byte limits, and global remote concurrency controls as other Apexify image workflows. Per-frame animation media is not retained in the shared byte/decode cache solely for reuse inside the sequence.


Frame reference

FieldRole
width, heightPer-frame PNG dimensions; in GIF mode they must equal the configured GIF dimensions.
durationGIF delay and, for non-GIF mode, optional wall-clock pacing delay.
backgroundColorSolid background.
gradientShared linear/radial/conic GradientConfig.
pattern{ source, repeat? } tiled image fill.
sourceFull-frame raster source.
blendModeGlobalCompositeOperation for source.
transformationsscaleX/Y, rotate (degrees), translateX/Y.
onDrawCustomImperative drawing hook receiving canvas context + canvas.

Example — PNG frames

SOURCEtypescript
typescript
Studio
const pngs = await painter.animate(  [    { backgroundColor: '#1e293b' },    {      gradient: {        type: 'conic',        centerX: 240,        centerY: 160,        startAngle: 45,        colors: [          { stop: 0, color: '#6366f1' },          { stop: 0.5, color: '#ec4899' },          { stop: 1, color: '#6366f1' },        ],      },      duration: 150,    },  ],  120,  480,  320);

Example — GIF file

SOURCEtypescript
typescript
Studio
await painter.animate(  [    { backgroundColor: '#020617', duration: 180 },    { backgroundColor: '#0f172a', source: './logo.png', duration: 180 },  ],  200,  640,  360,  {    gif: true,    gifPath: './out/tick-tock.gif',    onFrame: (i) => console.log('encoded frame', i),    onEnd: () => console.log('complete GIF is now on disk'),  });

Choosing animate vs createGIF

NeedPrefer
Chroma transparency / disposal codescreateGIF
Watermarks / rich textOverlaycreateGIF
Generated AsyncIterable streaming / backpressurecreateGIF
Buffer/base64/attachment outputcreateGIF
Quick declarative Frame sequence + optional GIF fileanimate
Separate PNG frames with per-frame dimensionsanimate
Encode frames to MP4createVideo({ createFromFrames }) or Scene → video

Next steps