Animation

PinePaper supports both simple loop animations and complex keyframe-based animations.

Simple Animations

animate(item, params)

Apply a predefined animation to an item.

Parameters:

  • item: Item to animate
  • params (object): Animation settings

Animation Types:

  • pulse - Scale up/down rhythmically
  • rotate - Continuous rotation
  • bounce - Vertical bouncing motion
  • fade - Opacity cycling
  • wobble - Side-to-side wobbling
  • slide - Horizontal sliding
  • typewriter - Character-by-character reveal (text only)
// Pulsing animation
app.animate(circle, {
  animationType: 'pulse',
  animationSpeed: 1.0  // Speed multiplier
});

// Rotating animation
app.animate(star, {
  animationType: 'rotate',
  animationSpeed: 0.5
});

// Bouncing text
app.animate(text, {
  animationType: 'bounce',
  animationSpeed: 1.5
});

// Typewriter effect
app.animate(text, {
  animationType: 'typewriter',
  animationSpeed: 10  // Characters per second
});

stopAnimation(item)

Stop and remove animation from an item.

app.stopAnimation(circle);

Keyframe Animations

For complex, multi-property animations over time.

Adding Keyframes

app.modify(sun, {
  animationType: 'keyframe',
  keyframes: [
    {
      time: 0,
      properties: { position: [100, 400], fillColor: '#fbbf24' },
      easing: 'linear'
    },
    {
      time: 2,
      properties: { position: [400, 100], fillColor: '#f97316' },
      easing: 'easeInOut'
    },
    {
      time: 4,
      properties: { position: [700, 400], fillColor: '#ef4444' },
      easing: 'easeOut'
    }
  ]
});

Keyframe Properties

Property Description
position Array [x, y] or separate x, y
scale Uniform scale, or scaleX/scaleY
rotation Rotation in degrees
opacity Transparency (0-1)
fillColor Fill color (hex, rgb, named)
strokeColor Stroke color
fontSize Text size

Easing Functions

  • linear - Constant speed
  • easeIn - Slow start
  • easeOut - Slow end
  • easeInOut - Slow start and end
  • bounce - Bounce effect
  • elastic - Elastic overshoot

Playback Control

// Start playback
app.playKeyframeTimeline(duration, loop);

// Stop playback
app.stopKeyframeTimeline();

// Scrub to specific time
app.setPlaybackTime(2.5);  // Jump to 2.5 seconds

// Check playback state
if (app.isPlayingKeyframes) {
  console.log('Playing at:', app.playbackTime);
}

Example: Day/Night Cycle

// Create sky
const sky = app.create('rectangle', {
  x: 400, y: 300, width: 800, height: 600,
  color: '#1e3a5f'
});

// Create sun
const sun = app.create('circle', {
  x: 100, y: 500, radius: 60,
  color: '#fbbf24'
});

// Animate sky color
app.modify(sky, {
  animationType: 'keyframe',
  keyframes: [
    { time: 0, properties: { fillColor: '#1e3a5f' }, easing: 'easeInOut' },
    { time: 2, properties: { fillColor: '#fb923c' }, easing: 'easeInOut' },
    { time: 4, properties: { fillColor: '#38bdf8' }, easing: 'easeInOut' },
    { time: 6, properties: { fillColor: '#f97316' }, easing: 'easeInOut' },
    { time: 8, properties: { fillColor: '#1e3a5f' }, easing: 'easeInOut' }
  ]
});

// Animate sun position
app.modify(sun, {
  animationType: 'keyframe',
  keyframes: [
    { time: 0, properties: { position: [100, 600], opacity: 0 } },
    { time: 2, properties: { position: [200, 100], opacity: 1 } },
    { time: 4, properties: { position: [400, 50], opacity: 1 } },
    { time: 6, properties: { position: [600, 100], opacity: 1 } },
    { time: 8, properties: { position: [700, 600], opacity: 0 } }
  ]
});

// Play looping
app.playKeyframeTimeline(8, true);

OnFrame Callbacks

For custom per-frame animation logic:

// Add custom animation
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,  // Minimum ms between calls
  priority: 0      // Execution order
});

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

Animated Items Tracking

PinePaper optimizes performance by tracking only animated items:

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

// Rebuild tracking after scene changes
app.rebuildAnimatedItemsSet();