API Documentation

Complete reference for the PinePaper programmatic interface. Build animated graphics with code or integrate with AI agents.

Quick Start

PinePaper exposes a global window.PinePaper object. Access it from the browser console or your scripts:

// Access the PinePaper instance
const app = window.PinePaper;

// Create text
const text = app.create('text', {
  content: 'Hello World',
  x: 400,
  y: 300,
  fontSize: 48,
  color: '#3730a3'
});

// Add animation
app.animate(text, {
  animationType: 'pulse',
  animationSpeed: 1.0
});

// Export as SVG
app.exportAnimatedSVG();

Core API Methods

create(type, params)

Create a new item on the canvas.

Parameters:

  • type (string) - Item type: 'text', 'circle', 'star', 'rectangle', 'triangle', 'polygon', 'ellipse'
  • params (object) - Properties:
    • x, y - Position (default: canvas center)
    • content - Text content (for text items)
    • color - Fill color (default: fontColor config)
    • fontSize - Font size (for text)
    • fontFamily - Font family
    • radius, width, height - Shape dimensions
// Create text
const text = app.create('text', {
  content: 'Hello!',
  x: 400,
  y: 300,
  fontSize: 64,
  color: '#3730a3'
});

// Create circle
const circle = app.create('circle', {
  x: 200,
  y: 200,
  radius: 50,
  color: '#ef4444'
});

select(query, replace)

Select items on the canvas.

Parameters:

  • query - Item, 'all', or filter object
  • replace (boolean) - Clear existing selection (default: true)
// Select an item
app.select(item);

// Select all items
app.select('all');

// Select by type
app.select({ type: 'text' });

modify(changes)

Modify selected items.

Parameters:

  • changes (object) - Properties to modify:
    • fontSize, color, content, x, y, rotation
    • animationType, animationSpeed
app.modify({
  fontSize: 72,
  color: '#10b981',
  rotation: 45
});

animate(item, params)

Apply animation to an item.

Parameters:

  • item - Target item
  • params (object):
    • animationType - 'pulse', 'rotate', 'bounce', 'fade', 'wobble', 'swing', 'shake', 'jelly', 'typewriter', 'path'
    • animationSpeed - Speed multiplier (default: 1.0)
    • animationDelay - Start delay in seconds
app.animate(item, {
  animationType: 'pulse',
  animationSpeed: 1.5,
  animationDelay: 0.5
});

removeSelectedItems()

Remove all currently selected items from the canvas.

app.select('all');
app.removeSelectedItems();

nudgeSelection(dx, dy)

Move selected items by pixel offset.

// Move right 10px, down 5px
app.nudgeSelection(10, 5);

Timeline System NEW

The Timeline system enables sequential, choreographed animations with precise timing control. Access it via the Timeline panel in the editor or programmatically via window.timelineUI.

Timeline UI Components

The timeline interface provides visual control over animation timing:

  • Playhead - Red vertical line showing current time position
  • Tracks - Horizontal rows for each animatable item
  • Keyframes - Diamond markers indicating animation points
  • Interpolation Curves - Visual representation of easing between keyframes
  • Transport Controls - Play, pause, stop, and loop buttons

window.timelineUI

Global timeline controller object.

Methods:

  • play() - Start playback
  • pause() - Pause playback
  • stop() - Stop and reset to start
  • seek(time) - Jump to specific time in seconds
  • setDuration(seconds) - Set total timeline duration
  • addKeyframe(item, time, properties, easing) - Add keyframe
  • applyPreset(item, presetName) - Apply animation preset
// Access timeline
const timeline = window.timelineUI;

// Set 5 second duration
timeline.setDuration(5);

// Play the animation
timeline.play();

// Seek to 2.5 seconds
timeline.seek(2.5);

// Pause playback
timeline.pause();

Keyframe Animations NEW

addKeyframe(item, time, properties, easing)

Add a keyframe to an item at a specific time.

Parameters:

  • item - Target Paper.js item
  • time (number) - Time in seconds
  • properties (object) - Animatable properties:
    • opacity - 0 to 1
    • x, y - Position coordinates
    • scaleX, scaleY - Scale factors
    • rotation - Rotation in degrees
  • easing (string) - Easing function: 'linear', 'easeIn', 'easeOut', 'easeInOut'
// Fade in animation
timelineUI.addKeyframe(text, 0, { opacity: 0 }, 'linear');
timelineUI.addKeyframe(text, 2, { opacity: 1 }, 'easeOut');

// Move and scale animation
timelineUI.addKeyframe(shape, 0, { x: 100, scaleX: 0.5, scaleY: 0.5 }, 'linear');
timelineUI.addKeyframe(shape, 1.5, { x: 400, scaleX: 1, scaleY: 1 }, 'easeInOut');

applyPreset(item, presetName)

Apply a predefined animation preset to an item.

Available Presets:

  • 'fadeIn' - Fade from 0% to 100% opacity
  • 'fadeOut' - Fade from 100% to 0% opacity
  • 'fadeInOut' - Fade in, hold, fade out
  • 'popIn' - Scale up with bounce effect
  • 'slideIn' - Slide in from left
  • 'pulse' - Scale up and down continuously
// Apply fade in preset
timelineUI.applyPreset(text, 'fadeIn');

// Apply pop in effect
timelineUI.applyPreset(title, 'popIn');

// Apply continuous pulse
timelineUI.applyPreset(logo, 'pulse');

Easing Functions

Control how values interpolate between keyframes.

linear

Constant speed from start to end

easeIn

Starts slow, accelerates

easeOut

Starts fast, decelerates

easeInOut

Smooth acceleration and deceleration

Example: Sequential Text Reveal

Create a choreographed sequence where elements appear one after another:

// Create elements
const title = app.create('text', { content: 'Welcome', y: 200, fontSize: 64 });
const subtitle = app.create('text', { content: 'To PinePaper', y: 280, fontSize: 32 });
const cta = app.create('text', { content: 'Start Creating →', y: 400, fontSize: 24 });

// Set timeline duration
timelineUI.setDuration(4);

// Title: Fade in at 0-1s
timelineUI.addKeyframe(title, 0, { opacity: 0 }, 'linear');
timelineUI.addKeyframe(title, 1, { opacity: 1 }, 'easeOut');

// Subtitle: Fade in at 1-2s
timelineUI.addKeyframe(subtitle, 0, { opacity: 0 }, 'linear');
timelineUI.addKeyframe(subtitle, 1, { opacity: 0 }, 'linear');
timelineUI.addKeyframe(subtitle, 2, { opacity: 1 }, 'easeOut');

// CTA: Pop in at 2.5-3.5s
timelineUI.addKeyframe(cta, 0, { opacity: 0, scaleX: 0.5, scaleY: 0.5 }, 'linear');
timelineUI.addKeyframe(cta, 2.5, { opacity: 0, scaleX: 0.5, scaleY: 0.5 }, 'linear');
timelineUI.addKeyframe(cta, 3.5, { opacity: 1, scaleX: 1, scaleY: 1 }, 'easeOut');

// Play the sequence
timelineUI.play();

Drawing & Brush Styles

The drawing system uses Paper.js Path objects. AI agents and developers can create custom brush styles programmatically using the full power of Paper.js path manipulation.

setBackgroundMode(mode)

Switch between background modes.

Modes:

  • 'pattern' - Geometric pattern background
  • 'drawing' - Freehand drawing mode (activates drawing tool)
  • 'image' - Background image mode
// Activate drawing mode
app.setBackgroundMode('drawing');

Basic Path Drawing

Create paths programmatically using Paper.js:

// Basic freehand-style path
const path = new Path();
path.strokeColor = '#FF0000';
path.strokeWidth = 5;
path.strokeCap = 'round';
path.strokeJoin = 'round';

// Add points to create the path
path.add(new Point(100, 100));
path.add(new Point(150, 120));
path.add(new Point(200, 100));
path.add(new Point(250, 130));

// Smooth the path for organic look
path.smooth();

Brush Style Properties

Paper.js path properties for different brush effects:

strokeCap

'round', 'square', 'butt'

strokeJoin

'round', 'miter', 'bevel'

dashArray

[10, 5] for dashed lines

blendMode

'multiply', 'screen', 'overlay'

Brush Style Examples

// Dotted/Stipple brush
const dottedPath = new Path();
dottedPath.strokeColor = 'blue';
dottedPath.strokeWidth = 2;
dottedPath.dashArray = [2, 8]; // dot pattern

// Marker/Highlighter style (semi-transparent overlay)
const markerPath = new Path();
markerPath.strokeColor = new Color(1, 1, 0, 0.5); // Yellow with alpha
markerPath.strokeWidth = 20;
markerPath.strokeCap = 'round';
markerPath.blendMode = 'multiply';

// Neon glow effect
const neonPath = new Path();
neonPath.strokeColor = '#00ff00';
neonPath.strokeWidth = 3;
neonPath.shadowColor = '#00ff00';
neonPath.shadowBlur = 10;

// Spray/Airbrush effect (multiple small circles)
function sprayBrush(center, radius, density, color) {
  for (let i = 0; i < density; i++) {
    const offset = new Point({
      angle: Math.random() * 360,
      length: Math.random() * radius
    });
    const dot = new Path.Circle({
      center: center.add(offset),
      radius: Math.random() * 2 + 0.5,
      fillColor: color
    });
    dot.opacity = Math.random() * 0.5 + 0.2;
  }
}

// Usage: sprayBrush(new Point(200, 200), 30, 50, '#ff6b6b');

Drawing Configuration

Get and set drawing settings:

Methods:

  • getDrawColor() - Get current stroke color
  • setDrawColor(color) - Set stroke color
  • getDrawWidth() - Get current stroke width
  • setDrawWidth(width) - Set stroke width
  • getBackgroundMode() - Get current mode
// Get current settings
const color = app.getDrawColor();
const width = app.getDrawWidth();
const mode = app.getBackgroundMode();

// Modify settings
app.setDrawColor('#3b82f6');
app.setDrawWidth(8);

clearDrawings()

Clear all freehand drawings from the canvas.

app.clearDrawings();

Background Generators Enhanced

PinePaper provides procedural background generators that create dynamic, customizable backgrounds using Paper.js. These functions use advanced distribution algorithms (Poisson disk sampling, Golden Ratio) for natural-looking results.

executeGenerator(name, params)

Execute a background generator function with custom parameters.

Available Generators:

'drawSunburst'

Radiating rays from center with customizable colors

'drawSunsetScene'

Animated sunset with gradient sky and clouds

'drawGrid' New

Lines, dots, or squares grid pattern

'drawStackedCircles' New

Overlapping circles with Poisson/Golden distribution

'drawCircuit' New

Tech circuit board pattern with animated bolt

'drawWaves' New

Layered wave pattern with gradient colors

'drawPattern'

Geometric shapes in circular orbit

// Stacked circles with Poisson disk sampling
app.executeGenerator('drawStackedCircles', {
  colors: ['#4f46e5', '#7c3aed', '#2563eb', '#0891b2'],
  count: 50,
  distribution: 'poisson',  // 'poisson', 'golden', 'random'
  minRadius: 20,
  maxRadius: 100,
  bgColor: '#1a1a2e'
});

// Circuit board pattern
app.executeGenerator('drawCircuit', {
  lineColor: '#22c55e',
  bgColor: '#0a0a0a',
  nodeSize: 6,
  gridSize: 60,
  animated: true
});

// Grid pattern
app.executeGenerator('drawGrid', {
  gridType: 'dots',  // 'lines', 'dots', 'squares'
  spacing: 40,
  lineColor: '#374151',
  bgColor: '#1f2937'
});

Distribution Utilities New

Low-level utilities for procedural placement of elements.

Available Methods:

  • poissonDiskSampling(width, height, minDist)

    Generates natural-looking random distribution using Bridson's algorithm. Points are guaranteed to be at least minDist apart.

  • goldenRatioDistribution(count, width, height)

    Generates points in a golden ratio spiral pattern for elegant distribution.

  • simpleNoise(x, y, scale)

    Returns pseudo-random noise value (0-1) for position-based variation.

  • getColorFromPosition(colors, x, y, noiseScale)

    Selects color from palette based on position for coherent color regions.

// Generate Poisson disk points for natural placement
const points = app.poissonDiskSampling(800, 600, 50);

// Place shapes at each point with noise-based variation
points.forEach(p => {
  const noise = app.simpleNoise(p.x, p.y, 100);
  const radius = 10 + noise * 40;
  const color = app.getColorFromPosition(['#ff0', '#0ff', '#f0f'], p.x, p.y);

  new Path.Circle({
    center: [p.x, p.y],
    radius: radius,
    fillColor: color,
    parent: app.patternGroup
  });
});

getAvailableGenerators()

Get list of all available background generators with their parameter schemas.

const generators = app.getAvailableGenerators();
// Returns array of:
// {
//   name: 'drawStackedCircles',
//   displayName: 'Stacked Circles',
//   description: 'Overlapping circles with glow',
//   params: {
//     colors: { type: 'colors', default: [...], description: '...' },
//     count: { type: 'number', min: 10, max: 100, default: 30 },
//     distribution: { type: 'select', options: ['poisson', 'golden', 'random'] }
//   }
// }

setBackgroundColor(color)

Set solid background color.

app.setBackgroundColor('#0B0F19');

Arrow System

arrowSystem.createArrow(points, config)

Create an arrow programmatically with full configuration.

Configuration Options:

Path Options:

  • lineColor - Stroke color (default: '#60a5fa')
  • lineWidth - Line thickness (default: 3)
  • style - 'straight', 'orthogonal', 'smooth'
  • smoothness - Curve factor 0-100 (for smooth)

Head Options:

  • headStyle - 'classic', 'stealth', 'open', 'none'
  • headSize - Size multiplier (default: 1.0)

Bolt Effect:

  • boltEnabled - Enable animated bolt
  • boltColor - Bolt color (default: '#fbbf24')
  • boltSpeed - Animation speed
// Create arrow with full config
app.arrowSystem.createArrow(
  [new Point(100, 100), new Point(200, 150), new Point(300, 100)],
  {
    lineColor: '#60a5fa',
    lineWidth: 3,
    style: 'smooth',
    smoothness: 60,
    headStyle: 'stealth',  // 'classic', 'stealth', 'open', 'none'
    headSize: 1.2,
    boltEnabled: true,
    boltColor: '#fbbf24'
  }
);

Arrowhead Styles

'classic'

Filled triangle

'stealth'

Diamond with notch

'open'

V-shape stroke

'none'

No arrowhead

arrowSystem.updateArrowSettings(config)

Update settings for selected arrow or set defaults for new arrows.

// Update arrow settings
app.arrowSystem.updateArrowSettings({
  headStyle: 'stealth',
  lineWidth: 5,
  lineColor: '#ef4444'
});

Arrow Management

// Get all arrows
const arrows = app.arrowSystem.arrows;

// Clear all arrows
app.arrowSystem.clear();

// Remove specific arrow
app.arrowSystem.removeArrow(arrow);

Effect System

applyEffect(item, effectType, config)

Apply particle effects to any item (text, shapes, arrows).

Available Effects:

  • 'sparkle' - Animated bolt that follows item path/bounds with particle trail
  • 'blast' - One-shot explosion of particles from item center

Sparkle Config:

app.applyEffect(item, 'sparkle', {
  color: '#fbbf24',  // Particle/glow color
  speed: 1.0,        // Animation speed multiplier
  size: 3            // Bolt size
});

Blast Config:

app.applyEffect(item, 'blast', {
  color: '#ef4444',  // Particle color
  radius: 100,       // Blast radius/speed
  count: 20          // Number of particles
});

effectSystem.removeEffectsFrom(item)

Remove all effects from an item.

// Remove effects from specific item
app.effectSystem.removeEffectsFrom(item);

// Clear ALL effects
app.effectSystem.clear();

Effect Lifecycle

Effects are automatically managed:

  • Sparkle effects persist and are saved with templates
  • Blast effects are transient (one-shot, not saved)
  • • Effects are stored in item.data.effects array
  • • Call effectSystem.rebuildEffects() after loading scenes

Scene Management

getSceneState()

Get a semantic description of the current scene for AI agents.

const state = app.getSceneState();
// Returns: { canvas, items, arrows, background }

exportTemplate()

Export current scene as a template (full state capture).

const template = app.exportTemplate();
// Save template JSON for later restoration

loadScene(templateData)

Load a complete scene from template data.

app.loadScene(templateData);

clearCanvas()

Clear all items and reset the canvas.

app.clearCanvas();

View Controls

Zoom Methods

  • zoomIn() - Zoom in by 20%
  • zoomOut() - Zoom out by 20%
  • resetZoom() - Reset to 1:1 zoom
app.zoomIn();
app.zoomOut();
app.resetZoom();

Paper.js Integration

PinePaper is built on Paper.js, a powerful vector graphics scripting framework. All Paper.js globals are available in the editor scope.

Key Paper.js Objects

Item Creation

// Create shapes directly
const circle = new Path.Circle({
  center: [200, 200],
  radius: 50,
  fillColor: '#3b82f6'
});

// Create text
const text = new PointText({
  point: [100, 100],
  content: 'Hello',
  fillColor: 'black',
  fontSize: 24
});

Common Item Properties

  • item.position - Get/set position as Point
  • item.fillColor - Fill color
  • item.strokeColor - Stroke color
  • item.strokeWidth - Stroke width
  • item.opacity - Opacity (0-1)
  • item.visible - Visibility boolean
  • item.rotation - Rotation in degrees
  • item.scaling - Scale factor
  • item.bounds - Bounding rectangle

View & Project

// Access view
view.center; // Canvas center point
view.size;   // Canvas size
view.zoom;   // Current zoom level

// Access project
project.activeLayer; // Current active layer
project.exportSVG(); // Export as SVG

Custom Events

// Listen to PinePaper events
window.addEventListener('selectionChanged', (e) => {
  console.log('Selected:', e.detail.items);
});

window.addEventListener('modeChanged', (e) => {
  console.log('Mode:', e.detail.mode);
});

Item Registry NEW

Overview

The Item Registry tracks all canvas items with unique IDs, types, and declarative associations. Every item (text, shape, generator element) is automatically registered.

Querying Items

// Get all registered items
const allItems = app.itemRegistry.getAll();

// Get items by type
const textItems = app.itemRegistry.getByType('text');
const circles = app.itemRegistry.getByType('circle');

// Get items by source
const userItems = app.itemRegistry.getBySource('user');
const generatorItems = app.itemRegistry.getBySource('generator');

// Get item by ID
const item = app.itemRegistry.getItem('item_5');

// Check if item exists
if (app.itemRegistry.has('item_5')) { /* ... */ }

// Get item count
const count = app.itemRegistry.count();

// Custom query with predicate
const largeItems = app.itemRegistry.query(entry =>
  entry.item.bounds.width > 100
);

Item Associations

Create relationships between items for complex scene logic:

// Create relationships
app.itemRegistry.addAssociation('item_1', 'item_2', 'orbits');
app.itemRegistry.addAssociation('item_1', 'item_3', 'contains');

// Query associations
const orbiting = app.itemRegistry.getAssociations('item_1', 'orbits');
const allRelated = app.itemRegistry.getAssociations('item_1');

// Remove association
app.itemRegistry.removeAssociation('item_1', 'item_2', 'orbits');

Item Properties & Names

// Update item properties
app.itemRegistry.updateProperties('item_1', {
  customProp: 'value',
  animationType: 'pulse'
});

// Set item name for identification
app.itemRegistry.setName('item_1', 'MainTitle');

// Find by name
const headers = app.itemRegistry.getByName('MainTitle');

Relation System NEW

The Relation System enables behavior-driven associations between items. Define relationships like "orbits", "follows", or "attached_to" and items will automatically animate based on their relations.

Built-in Relations

'orbits'

Circular motion around target (radius, speed, direction)

'follows'

Move toward target with smoothing (offset, delay)

'attached_to'

Move with target like parent-child (offset)

'maintains_distance'

Stay fixed distance from target (distance, strength)

'points_at'

Rotate to face target (offset_angle)

'mirrors'

Mirror position across axis (vertical, horizontal)

addRelation(fromId, toId, relation, params)

Create a behavior-driven relationship between two items.

// Make moon orbit earth
app.addRelation(moonId, earthId, 'orbits', {
  radius: 100,
  speed: 0.5,
  direction: 'counterclockwise'
});

// Make label follow player
app.addRelation(labelId, playerId, 'follows', {
  offset: [0, -50],  // 50px above
  smoothing: 0.1
});

// Attach name tag to character
app.addRelation(nameTagId, characterId, 'attached_to', {
  offset: [0, -30]
});

Querying Relations

// Get all relations for an item
const relations = app.getRelations(itemId);

// Get specific relation type
const orbits = app.getRelations(itemId, 'orbits');

// Find items that orbit a specific target
const orbiters = app.queryByRelationTarget(earthId, 'orbits');

// Check if relation exists
if (app.hasRelation(moonId, earthId, 'orbits')) {
  console.log('Moon orbits Earth');
}

// Remove a relation
app.removeRelation(moonId, earthId, 'orbits');

Advanced Queries

Powerful query methods for complex scene analysis:

// Compound query with multiple conditions
const results = app.queryCompound([
  { relation: 'orbits' },              // Must orbit something
  { relation: 'follows', not: true }   // Must NOT follow anything
]);

// Chain query - follow relation chains
const chains = app.queryRelationChain(['follows', 'orbits']);

// Find items with no relations
const isolated = app.queryIsolatedItems();

// Get relation statistics
const stats = app.getRelationStats();
// { activeItems: 5, ruleCount: 8, associationsByType: {...} }

Example: Solar System

// Create celestial bodies
const sun = app.create('circle', { x: 400, y: 300, radius: 50, color: '#fbbf24' });
const earth = app.create('circle', { x: 550, y: 300, radius: 20, color: '#3b82f6' });
const moon = app.create('circle', { x: 580, y: 300, radius: 8, color: '#9ca3af' });

// Get registry IDs
const sunId = sun.data.registryId;
const earthId = earth.data.registryId;
const moonId = moon.data.registryId;

// Earth orbits sun
app.addRelation(earthId, sunId, 'orbits', { radius: 150, speed: 0.1 });

// Moon orbits earth
app.addRelation(moonId, earthId, 'orbits', { radius: 30, speed: 0.3 });

// Label attached to earth
const label = app.create('text', { content: 'Earth', x: 550, y: 330, fontSize: 12 });
app.addRelation(label.data.registryId, earthId, 'attached_to', { offset: [0, 25] });

Worker Pool NEW

Overview

Web Worker pool for offloading heavy computations. Keeps the UI responsive during complex operations. Includes automatic fallback to main thread if Workers are unavailable.

Available Operations

// Poisson disk sampling (non-blocking)
const points = await app.workerPool.poissonDiskSampling(800, 600, 50);

// Golden ratio distribution
const spiralPoints = await app.workerPool.goldenRatioDistribution(100, 800, 600);

// Path simplification (Douglas-Peucker algorithm)
const simplified = await app.workerPool.simplifyPath(points, 5);

// Color calculations for large datasets
const colors = await app.workerPool.calculateColors(positions, palette, 0.01);

// Scene serialization (for large scenes)
const json = await app.workerPool.serializeScene(sceneData);
const scene = await app.workerPool.deserializeScene(jsonString);

Worker Status & Statistics

// Check if workers are available
if (app.workerPool.isAvailable()) {
  // Use async methods
}

// Get performance statistics
const stats = app.workerPool.getStats();
// { tasksCompleted, tasksFailed, averageTaskTime, workersActive, ... }

// Warm up workers (pre-initialize)
await app.workerPool.warmUp();

Async Generators NEW

Using Async Generators

Async generators leverage Web Workers for computation-heavy operations, keeping the UI responsive.

// Execute async generator (non-blocking)
await app.generatorRegistry.executeAsync('drawPoissonShapesAsync', {
  shape: 'circle',
  minDistance: 40,
  colors: ['#8B5CF6', '#A78BFA']
});

Creating Custom Async Generators

app.generatorRegistry.register('myAsyncGenerator', {
  displayName: 'My Async Pattern',
  description: 'Pattern using Web Workers',
  category: 'custom',
  async: true,  // Mark as async
  params: {
    count: { type: 'number', default: 100 }
  },
  fn: async function(ctx) {
    const { params, register, patternGroup, view } = ctx;

    // Use async helpers from WorkerPool
    let points;
    if (ctx.async && ctx.async.poissonDiskSampling) {
      // Non-blocking computation via Web Worker
      points = await ctx.async.poissonDiskSampling(
        view.size.width, view.size.height, 30, 30
      );
    }

    // Create visuals on main thread
    const group = new Group({ parent: patternGroup });
    points.forEach(pt => {
      new Path.Circle({
        center: [pt.x, pt.y],
        radius: 5,
        fillColor: '#8B5CF6',
        parent: group
      });
    });

    register(group, 'myPattern', { count: points.length });
  }
});

Async Context Helpers:

  • ctx.async.poissonDiskSampling(w, h, dist, attempts) - Poisson sampling
  • ctx.async.goldenRatioDistribution(count, w, h, padding) - Golden spiral
  • ctx.async.simplifyPath(points, tolerance) - Path simplification
  • ctx.async.calculateGeneratorPositions(config) - Generic position calc

OnFrame Callbacks NEW

Overview

Register custom animation callbacks with built-in performance guardrails. Callbacks are automatically monitored and can be disabled if they impact performance.

// Add a callback (called every frame)
app.addOnFrameCallback('myAnimation', (event) => {
  // event.delta - time since last frame
  // event.time - total elapsed time
  // event.count - frame count
  myItem.rotate(event.delta * 30); // 30 degrees per second
}, {
  throttleMs: 16,  // Optional: minimum ms between calls
  priority: 0      // Optional: execution order
});

// Remove callback
app.removeOnFrameCallback('myAnimation');

// Remove all callbacks
app.clearOnFrameCallbacks();

Features:

  • Built-in performance monitoring
  • Automatic throttling for slow callbacks
  • Priority-based execution order

Animation Optimization

// Get count of animated items
const count = app.getAnimatedItemCount();

// Stop all animations on an item
app.stopAnimation(item);

// Rebuild animation tracking (after scene load)
app.rebuildAnimatedItemsSet();

Complete Example

Create an animated greeting card with timeline sequencing:

// Set background
app.setBackgroundGenerator('drawSunsetScene');

// Create title
const title = app.create('text', {
  content: 'Happy Birthday!',
  y: 200,
  fontSize: 72,
  color: '#ffffff'
});

// Create subtitle
const subtitle = app.create('text', {
  content: 'Hope your day is amazing!',
  y: 400,
  fontSize: 32,
  color: '#fbbf24'
});

// Set up timeline (4 seconds)
timelineUI.setDuration(4);

// Animate title: fade in 0-1s
timelineUI.addKeyframe(title, 0, { opacity: 0 }, 'linear');
timelineUI.addKeyframe(title, 1, { opacity: 1 }, 'easeOut');

// Animate subtitle: fade in 1.5-2.5s
timelineUI.addKeyframe(subtitle, 0, { opacity: 0 }, 'linear');
timelineUI.addKeyframe(subtitle, 1.5, { opacity: 0 }, 'linear');
timelineUI.addKeyframe(subtitle, 2.5, { opacity: 1 }, 'easeOut');

// Add sparkles
app.applyEffect(title, 'sparkle', {
  count: 30,
  color: '#fbbf24'
});

// Play and export
timelineUI.play();
// app.exportAnimatedSVG();