FasterMotion
API ReferenceCore

Tween - Advanced Features

Callbacks, playback control, iterations, and advanced animation features

Advanced features for controlling and sequencing animations with FasterMotion.

Callbacks

Execute functions at specific points in the animation lifecycle.

onStart

Type: () => void

Called when animation begins playing.

FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 1000,
  onStart: () => {
    console.log('Animation started!');
  }
});

Try it: onStart Callback

FasterMotion.dom({
  target: '#box',
  to: { x: 200, rotation: 360 },
  duration: 1000,
  onStart: () => {
    console.log('Animation started!');
  }
});

onUpdate

Type: (progress: number) => void

Called on every frame during animation. Receives progress (0 to 1).

FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 1000,
  onUpdate: (progress) => {
    console.log('Progress:', Math.round(progress * 100) + '%');
  }
});

onComplete

Type: () => void

Called when animation completes.

FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 1000,
  onComplete: () => {
    console.log('Animation complete!');
  }
});

Try it: onComplete Callback

FasterMotion.dom({
  target: '#box',
  to: { x: 200, scale: 1.5 },
  duration: 1000,
  ease: 'back.out',
  onComplete: () => {
    console.log('Done!');
  }
});

All Callbacks Together

FasterMotion.dom({
  target: '#box',
  to: { x: 200, rotation: 360 },
  duration: 2000,
  onStart: () => {
    console.log('Starting...');
  },
  onUpdate: (progress) => {
    const percent = Math.round(progress * 100);
    document.querySelector('#progress').textContent = percent + '%';
  },
  onComplete: () => {
    console.log('Finished!');
  }
});

Try it: All Callbacks with Progress

FasterMotion.dom({
  target: '#box',
  to: { x: 200, rotation: 360 },
  duration: 2000,
  onStart: () => {
    console.log('Starting...');
  },
  onUpdate: (progress) => {
    const percent = Math.round(progress * 100);
    document.querySelector('#progress').textContent = percent + '%';
  },
  onComplete: () => {
    console.log('Finished!');
  }
});

Playback Control

Control animation playback with methods returned from FasterMotion.dom().

Basic Control Methods

const anim = FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 2000
});

// Pause the animation
anim.pause();

// Resume from current position
anim.play();

// Stop and reset to start
anim.stop();

// Restart from beginning
anim.restart();

Try it: Playback Control

const anim = FasterMotion.dom({
  target: '#box',
  to: { x: 200, rotation: 360 },
  duration: 2000,
  ease: 'linear'
});

// Control buttons available: Start, Pause, Resume, Stop, Restart

seek()

Type: (time: number) => void

Jump to specific time in animation (milliseconds).

const anim = FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 2000
});

// Jump to halfway point
anim.seek(1000);

// Jump to 75% complete
anim.seek(1500);

progress()

Type: (value?: number) => number

Get or set animation progress (0 to 1).

const anim = FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 2000
});

// Get current progress
const current = anim.progress(); // 0 to 1

// Set to 50% complete
anim.progress(0.5);

// Set to end
anim.progress(1);

reverse()

Type: () => void

Reverse the animation direction.

const anim = FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 1000
});

// Reverse direction
anim.reverse();

// Play backwards
anim.play();

Try it: Reverse Animation

const anim = FasterMotion.dom({
  target: '#box',
  to: { x: 200, rotation: 360 },
  duration: 1500,
  ease: 'curve2.inOut'
});

// Reverse after 800ms
setTimeout(() => anim.reverse(), 800);

Iterations

Repeat animations with different patterns.

repeat

Type: number | Default: 0

Number of times to repeat. Use -1 for infinite.

// Repeat 3 times
FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 1000,
  repeat: 3
});

// Infinite loop
FasterMotion.dom({
  target: '#box',
  to: { rotation: 360 },
  duration: 2000,
  repeat: -1,
  ease: 'linear'
});

Try it: Repeat Animation

FasterMotion.dom({
  target: '#box',
  to: { rotation: 360 },
  duration: 1500,
  repeat: 2,
  ease: 'linear'
});

yoyo

Type: boolean | Default: false

Alternate direction on each repeat.

// Bounce back and forth
FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 1000,
  repeat: 3,
  yoyo: true,
  ease: 'curve2.inOut'
});

Try it: Yoyo Animation

FasterMotion.dom({
  target: '#box',
  to: { x: 200, scale: 1.5 },
  duration: 800,
  repeat: 3,
  yoyo: true,
  ease: 'curve2.inOut'
});

repeatDelay

Type: number | Default: 0

Delay between repeats (milliseconds).

FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 1000,
  repeat: -1,
  repeatDelay: 500  // Wait 500ms between repeats
});

Infinite Pulse

// Pulsing scale animation
FasterMotion.dom({
  target: '.button',
  to: { scale: 1.1 },
  duration: 600,
  repeat: -1,
  yoyo: true,
  ease: 'curve2.inOut'
});

Try it: Infinite Pulse

FasterMotion.dom({
  target: '#box',
  to: { scale: 1.3, opacity: 0.7 },
  duration: 800,
  repeat: -1,
  yoyo: true,
  ease: 'curve2.inOut'
});

Advanced Patterns

Chained Animations

// Sequence using onComplete
FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 1000,
  onComplete: () => {
    FasterMotion.dom({
      target: '#box',
      to: { y: 100 },
      duration: 1000
    });
  }
});

Pause on Hover

const anim = FasterMotion.dom({
  target: '#box',
  to: { rotation: 360 },
  duration: 3000,
  repeat: -1,
  ease: 'linear'
});

const box = document.querySelector('#box');
box.addEventListener('mouseenter', () => anim.pause());
box.addEventListener('mouseleave', () => anim.play());

Try it: Pause on Hover

const anim = FasterMotion.dom({
  target: '#box',
  to: { rotation: 360 },
  duration: 3000,
  repeat: -1,
  ease: 'linear'
});

const box = document.querySelector('#box');
box.addEventListener('mouseenter', () => anim.pause());
box.addEventListener('mouseleave', () => anim.play());

Progress Bar

const progressBar = document.querySelector('.progress');

FasterMotion.dom({
  target: '#box',
  to: { x: 500 },
  duration: 3000,
  onUpdate: (progress) => {
    progressBar.style.width = (progress * 100) + '%';
  }
});

Try it: Progress Bar

const progressBar = document.querySelector('.progress-bar');

FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 3000,
  onUpdate: (progress) => {
    progressBar.style.width = (progress * 100) + '%';
  }
});

Scrubber Control

const anim = FasterMotion.dom({
  target: '#box',
  to: { x: 300, rotation: 360 },
  duration: 2000
});

// Pause immediately for scrubbing
anim.pause();

// Slider input
const slider = document.querySelector('#scrubber');
slider.addEventListener('input', (e) => {
  anim.progress(e.target.value / 100);
});

Try it: Scrubber Control

const anim = FasterMotion.dom({
  target: '#box',
  to: { x: 200, rotation: 360 },
  duration: 2000
});

// Pause immediately for scrubbing
anim.pause();

// Slider input
const slider = document.querySelector('#scrubber');
slider.addEventListener('input', (e) => {
  anim.progress(e.target.value / 100);
});

Dynamic Duration

// Speed based on distance
function animateToPosition(x, y) {
  const box = document.querySelector('#box');
  const rect = box.getBoundingClientRect();

  const dx = x - rect.left;
  const dy = y - rect.top;
  const distance = Math.sqrt(dx * dx + dy * dy);

  // 1px per millisecond
  const duration = distance;

  FasterMotion.dom({
    target: box,
    to: { x, y },
    duration,
    ease: 'curve2.out'
  });
}

document.addEventListener('click', (e) => {
  animateToPosition(e.clientX, e.clientY);
});

State Machine

let state = 'idle';

const states = {
  idle: () => {
    FasterMotion.dom({
      target: '#box',
      to: { scale: 1, rotation: 0 },
      duration: 300
    });
  },
  hover: () => {
    FasterMotion.dom({
      target: '#box',
      to: { scale: 1.2, rotation: 5 },
      duration: 300,
      ease: 'back.out'
    });
  },
  active: () => {
    FasterMotion.dom({
      target: '#box',
      to: { scale: 0.95, rotation: 0 },
      duration: 200
    });
  }
};

// Trigger state changes
function setState(newState) {
  state = newState;
  states[state]();
}

// Usage
box.addEventListener('mouseenter', () => setState('hover'));
box.addEventListener('mouseleave', () => setState('idle'));
box.addEventListener('mousedown', () => setState('active'));
box.addEventListener('mouseup', () => setState('hover'));

Multi-Element Coordination

// Stagger multiple elements
const boxes = document.querySelectorAll('.box');

boxes.forEach((box, i) => {
  FasterMotion.dom({
    target: box,
    to: { x: 200, rotation: 360 },
    duration: 1000,
    delay: i * 100,  // Stagger by index
    ease: 'back.out'
  });
});

Update While Running

const anim = FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 2000
});

// Change target mid-animation
setTimeout(() => {
  anim.stop();
  FasterMotion.dom({
    target: '#box',
    to: { x: 100, y: 100 },  // New target
    duration: 1000
  });
}, 1000);

Performance Tips

Batch Similar Animations

// Good: Single selector for multiple elements
FasterMotion.dom({
  target: '.boxes',
  to: { x: 100 },
  duration: 1000
});

// Avoid: Individual animations
document.querySelectorAll('.boxes').forEach(box => {
  FasterMotion.dom({
    target: box,
    to: { x: 100 },
    duration: 1000
  });
});

Cleanup Animations

// Store reference for cleanup
const animations = [];

function createAnimation() {
  const anim = FasterMotion.dom({
    target: '#box',
    to: { x: 200 },
    duration: 1000
  });
  animations.push(anim);
}

// Cleanup on unmount/destroy
function cleanup() {
  animations.forEach(anim => anim.stop());
  animations.length = 0;
}

Prefer Transform Properties

// Good: GPU-accelerated
FasterMotion.dom({
  target: '#box',
  to: { x: 100, scale: 1.2, rotation: 45 }
});

// Avoid: Triggers layout
FasterMotion.dom({
  target: '#box',
  to: { left: '100px', width: '200px' }
});

Use will-change CSS

/* Hint browser to optimize */
.animated {
  will-change: transform, opacity;
}

/* Remove after animation */
.animated.complete {
  will-change: auto;
}

Common Mistakes

Avoid

// Missing cleanup
setInterval(() => {
  FasterMotion.dom({ target: '#box', to: { x: 100 } });
}, 1000);

// Conflicting animations
FasterMotion.dom({ target: '#box', to: { x: 100 } });
FasterMotion.dom({ target: '#box', to: { x: 200 } }); // Conflicts!

// Animating layout properties
FasterMotion.dom({ target: '#box', to: { width: '500px' } });

Better

// Store and cleanup
const anim = FasterMotion.dom({ target: '#box', to: { x: 100 } });
// Later: anim.stop();

// Stop previous before new
if (currentAnim) currentAnim.stop();
currentAnim = FasterMotion.dom({ target: '#box', to: { x: 200 } });

// Use transform
FasterMotion.dom({ target: '#box', to: { scaleX: 1.5 } });

TypeScript Support

import { FasterMotion, type Animation } from 'faster-motion';

// Typed animation reference
const anim: Animation = FasterMotion.dom({
  target: '#box',
  to: { x: 200, rotation: 360 },
  duration: 1000,
  ease: 'spring.bouncy',
  onUpdate: (progress: number) => {
    console.log(progress);
  },
  onComplete: () => {
    console.log('Done');
  }
});

// Type-safe control
anim.pause();
anim.play();
anim.progress(0.5);

See Also

On this page