FasterMotion
API ReferencePath Animation

PathFollow Advanced

Manual control, stagger animations, and element distribution

Advanced PathFollow features including manual progress control, stagger animations, element distribution, and canvas integration.

Manual Control

Create a PathFollow instance for direct progress manipulation.

PathFollow.create()

import { PathFollow } from 'faster-motion';

const follower = PathFollow.create('#car', {
  path: '#road',
  autoRotate: true
});

// Update progress directly
follower.setProgress(0.5);  // Move to 50%

Try it: Slider Control

const follower = PathFollow.create('#element', {
  path: '#curve-path',
  autoRotate: true
});

slider.addEventListener('input', (e) => {
  const progress = e.target.value / 100;
  follower.setProgress(progress);
});

PathFollowInstance Properties

Property/MethodTypeDescription
elementElementThe target element
resourcePathResourceThe path resource
progressnumberCurrent progress (read/write)
setProgress(value)voidSet progress and update position
update()voidForce position update
getPosition()SampleResultGet current sample without updating
getOffsetPosition()Vec2Get position with offsets applied
destroy()voidCleanup instance

Scroll-Linked Animation

Link path progress to scroll position:

const follower = PathFollow.create('#indicator', {
  path: '#scroll-path',
  autoRotate: false
});

window.addEventListener('scroll', () => {
  const scrollProgress = window.scrollY /
    (document.body.scrollHeight - window.innerHeight);
  follower.setProgress(scrollProgress);
});

Drag-to-Follow

Let users drag an element along a path:

const follower = PathFollow.create('#draggable', {
  path: '#guide-path'
});

element.addEventListener('drag', (e) => {
  // Find closest point on path
  const closest = follower.resource.getClosestPoint(e.clientX, e.clientY);
  follower.setProgress(closest.progress);
});

Stagger Animations

Animate multiple elements along the same path with staggered timing.

PathFollow.stagger()

PathFollow.stagger('.birds', {
  path: '#flight-path',
  progress: 1,
  duration: 3000,
  stagger: 200  // 200ms between each bird
});

StaggerOptions

OptionTypeDescription
eachnumberDelay between elements (ms)
from'start' | 'end' | 'center' | 'edges' | numberStarting position
grid[rows, cols]2D grid pattern
// Stagger from center
PathFollow.stagger('.elements', {
  path: '#path',
  progress: 1,
  stagger: { each: 100, from: 'center' }
});

// Reverse order
PathFollow.stagger('.elements', {
  path: '#path',
  progress: 1,
  stagger: { each: 100, from: 'end' }
});

Element Distribution

Instantly position elements at evenly-spaced points along a path.

PathFollow.distribute()

const instances = PathFollow.distribute('.dots', {
  path: '#circle-path',
  mode: 'even',      // Evenly spaced
  autoRotate: true
});

// Returns array of PathFollowInstance for manual control

Try it: Distributed Elements

// Distribute planets around orbit
const planets = PathFollow.distribute('.planet', {
  path: '#orbit',
  mode: 'even',
  autoRotate: false
});

// Animate all planets together
function animate() {
  planets.forEach((p, i) => {
    p.progress += 0.001 * (i + 1);  // Different speeds
  });
  requestAnimationFrame(animate);
}
animate();

Distribution Modes

ModeDescription
'even'Evenly spaced along entire path
'start'Clustered at start of path
'center'Clustered at center of path
'end'Clustered at end of path
// Even distribution
PathFollow.distribute('.items', { path: '#path', mode: 'even' });

// All at start, ready to animate
PathFollow.distribute('.items', { path: '#path', mode: 'start' });

Canvas PathFollow

For canvas-based animations using .fmtion files.

fmtion PathFollowTrack Schema

interface PathFollowTrack {
  id: string;
  name?: string;
  target: string;           // "canvas://object/element-id"
  pathRef: string;          // "canvas://object/path-id" or "asset:path-id"
  autoRotate?: boolean;     // Default: true
  rotationOffset?: number;  // Degrees
  loop?: boolean;           // Default: true
  offsetX?: number;         // Pixels along tangent
  offsetY?: number;         // Pixels perpendicular
  enabled?: boolean;
  keyframes: PathFollowKeyframe[];
}

interface PathFollowKeyframe {
  time: number;      // Milliseconds
  progress: number;  // 0-1
  easing?: string;
}

Example .fmtion Track

{
  "pathFollowTracks": [{
    "id": "follow-1",
    "target": "canvas://object/rocket",
    "pathRef": "canvas://object/flight-path",
    "autoRotate": true,
    "rotationOffset": -90,
    "keyframes": [
      { "time": 0, "progress": 0 },
      { "time": 3000, "progress": 1, "easing": "easeInOut" }
    ]
  }]
}

Path References

PathFollow tracks can reference paths in two ways:

// Reference a canvas path object
"pathRef": "canvas://object/my-path"

// Reference a path asset
"pathRef": "asset:flight-path"

Combining with TrimPath

Animate an element following a path while the path draws:

import { TrimPath, PathFollow, PathResource } from 'faster-motion';

const path = new PathResource({ path: '#draw-path' });

// Draw the path
TrimPath.to('#draw-path', {
  start: 0,
  end: 100,
  duration: 2000
});

// Element follows the drawing tip
const follower = PathFollow.create('#pencil', {
  path,
  autoRotate: true,
  rotationOffset: -90
});

// Sync with trim progress
TrimPath.to('#draw-path', {
  end: 100,
  duration: 2000,
  onUpdate: (state) => {
    follower.setProgress(state.end / 100);
  }
});

Performance Tips

Share PathResource Instances

// Create once
const sharedPath = new PathResource({ path: '#orbit' });

// Reuse for all elements
document.querySelectorAll('.satellite').forEach(el => {
  PathFollow.to(el, { path: sharedPath, duration: 5000 });
});

Use distribute() for Static Positioning

// More efficient than individual setProgress calls
PathFollow.distribute('.markers', { path: '#path', mode: 'even' });

Cleanup Manual Instances

const follower = PathFollow.create('#element', { path: '#path' });

// When done
follower.destroy();

Clear Cache When Done

// After bulk animations complete
PathFollow.clearCache();

Common Patterns

Infinite Orbit

PathFollow.to('#satellite', {
  path: '#orbit',
  progress: [0, 1],
  duration: 10000,
  ease: 'linear',
  repeat: -1,
  loop: true
});

Train on Tracks

const trackPath = new PathResource({ path: '#railway' });

// Engine
PathFollow.to('#engine', {
  path: trackPath,
  progress: 1,
  duration: 5000
});

// Cars follow with delay
PathFollow.stagger('.train-car', {
  path: trackPath,
  progress: 1,
  duration: 5000,
  stagger: -200  // Negative = behind the engine
});

Scroll-Reveal Along Path

const follower = PathFollow.create('#reveal-indicator', {
  path: '#scroll-guide'
});

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    const progress = entry.intersectionRatio;
    follower.setProgress(progress);
  });
}, { threshold: Array.from({ length: 100 }, (_, i) => i / 100) });

observer.observe(document.querySelector('#content'));

TypeScript Support

import {
  PathFollow,
  PathFollowOptions,
  PathFollowInstance,
  PathResource,
  SampleResult
} from 'faster-motion';

const path: PathResource = new PathResource({ path: '#curve' });

const instance: PathFollowInstance = PathFollow.create('#element', {
  path,
  autoRotate: true
});

instance.setProgress(0.5);
const position: SampleResult = instance.getPosition();
instance.destroy();

// Distribute returns array of instances
const distributed: PathFollowInstance[] = PathFollow.distribute('.items', {
  path,
  mode: 'even'
});

See Also

On this page