Node / Gif Animation · guide
animate — declarative frame sequences
Current Apexify.js 6.0.0 documentation for animate — declarative frame sequences.
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
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
When gif: true, gifPath is required.
Hook order
onStartfires 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.onEndfires only after the loop. In GIF mode it runs afterencoder.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.gif | Return / pacing |
|---|---|
| false / omitted | Buffer[], one PNG per frame. After each frame, animate waits frame.duration ?? defaultDuration ms when the value is positive. |
| true | undefined. 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:
- choose
frame.width/heightor the defaults (non-GIF mode); GIF mode has already required the fixed output size - clear the canvas
- apply optional transform (
translate→rotate→scale) - resolve background paint:
- gradient through Apexify's shared gradient renderer (linear, radial, and conic)
- pattern, when supplied, replaces the gradient fill
- otherwise
backgroundColor
- draw optional
sourcewithblendMode(defaultsource-over) - invoke
onDrawCustom(ctx, canvas) - restore the transform
- encode the GIF frame or snapshot PNG bytes
- invoke
onFrame - 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
| Field | Role |
|---|---|
width, height | Per-frame PNG dimensions; in GIF mode they must equal the configured GIF dimensions. |
duration | GIF delay and, for non-GIF mode, optional wall-clock pacing delay. |
backgroundColor | Solid background. |
gradient | Shared linear/radial/conic GradientConfig. |
pattern | { source, repeat? } tiled image fill. |
source | Full-frame raster source. |
blendMode | GlobalCompositeOperation for source. |
transformations | scaleX/Y, rotate (degrees), translateX/Y. |
onDrawCustom | Imperative drawing hook receiving canvas context + canvas. |
Example — PNG frames
Example — GIF file
Choosing animate vs createGIF
| Need | Prefer |
|---|---|
| Chroma transparency / disposal codes | createGIF |
Watermarks / rich textOverlay | createGIF |
Generated AsyncIterable streaming / backpressure | createGIF |
| Buffer/base64/attachment output | createGIF |
Quick declarative Frame sequence + optional GIF file | animate |
| Separate PNG frames with per-frame dimensions | animate |
| Encode frames to MP4 | createVideo({ createFromFrames }) or Scene → video |