TrimPath Advanced
Stagger animations, sequential mode, and canvas integration
Advanced TrimPath features for complex animations including stagger effects, multi-path sequential mode, and canvas/fmtion integration.
Stagger Animations
Animate multiple paths with staggered timing.
TrimPath.stagger()
import { TrimPath } from 'faster-motion';
TrimPath.stagger('.stroke-paths', {
start: 0,
end: 100,
duration: 1000,
stagger: 150 // 150ms delay between each
});Try it: Staggered Drawing
TrimPath.stagger('.line', {
start: 0,
end: 100,
duration: 800,
ease: 'easeOut',
stagger: {
each: 100,
from: 'center' // Start from middle elements
}
});StaggerOptions
| Option | Type | Description |
|---|---|---|
each | number | Delay between each element (ms) |
from | 'start' | 'end' | 'center' | 'edges' | number | Starting position |
grid | [rows, cols] | 2D grid stagger pattern |
// Stagger from center outward
TrimPath.stagger('.paths', {
end: 100,
stagger: { each: 100, from: 'center' }
});
// Stagger from specific index
TrimPath.stagger('.paths', {
end: 100,
stagger: { each: 50, from: 3 } // Start from 4th element
});
// Grid stagger (for path grids)
TrimPath.stagger('.grid-paths', {
end: 100,
stagger: { each: 50, grid: [4, 6] } // 4 rows, 6 columns
});Parallel vs Sequential Mode
When animating multiple paths together, there are two modes:
Parallel Mode (Default)
Each path is trimmed independently using its own length.
// Both paths animate 0-100% simultaneously
// but each based on their own individual lengths
TrimPath.stagger([path1, path2], {
start: 0,
end: 100,
duration: 1000
});Sequential Mode
All paths are treated as one continuous path. Trim values are distributed based on relative lengths.
Try it: Sequential vs Parallel
// Sequential mode - paths draw one after another
// If 3 equal-length paths:
// - At 33% progress: First path fully drawn
// - At 66% progress: First + second paths drawn
// - At 100% progress: All three paths drawnHow Sequential Mode Works:
With 3 paths of lengths 100px, 120px, 100px (total: 320px):
| Global End | Path 1 (31%) | Path 2 (38%) | Path 3 (31%) |
|---|---|---|---|
| 0.25 | 80% visible | 0% visible | 0% visible |
| 0.50 | 100% visible | 50% visible | 0% visible |
| 0.75 | 100% visible | 100% visible | 35% visible |
| 1.00 | 100% visible | 100% visible | 100% visible |
This matches Lottie's "Trim Paths" behavior:
m=1→ Parallel (simultaneous)m=2→ Sequential
Offset Wrap-Around
When the offset value causes the visible region to wrap around the path:
// Offset causes wrap-around
TrimPath.to('#path', {
start: 0,
end: 30,
offset: 80, // 80% offset
duration: 1000
});
// Visible region: 80-100% AND 0-10%The dash pattern is automatically adjusted to show two segments when wrap-around occurs.
Canvas TrimPath
For canvas-based animations using .fmtion files.
MorphablePath Trim Properties
Canvas path objects support trim via direct properties:
// In canvas context
path.trimStart = 0.25; // 0-1 value
path.trimEnd = 0.75; // 0-1 value
path.trimOffset = 0; // 0-1 value (0 = 0°, 1 = 360°)fmtion TrimPathTrack Schema
interface TrimPathTrack {
id: string;
name?: string;
target: string; // "canvas://object/path-id"
simultaneous?: boolean; // true = parallel, false = sequential
enabled?: boolean;
keyframes: TrimPathKeyframe[];
}
interface TrimPathKeyframe {
time: number; // Milliseconds
start: number; // 0-1
end: number; // 0-1
offset: number; // 0-1
easing?: string;
}Example .fmtion Track
{
"trimPathTracks": [{
"id": "trim-1",
"target": "canvas://object/signature-path",
"simultaneous": true,
"keyframes": [
{ "time": 0, "start": 0, "end": 0, "offset": 0 },
{ "time": 1000, "start": 0, "end": 1, "offset": 0, "easing": "easeOut" }
]
}]
}Sequential Mode Utilities
FasterMotion exports utilities for calculating sequential trim values:
calculateSequentialPathInfo
Build path info with cumulative offsets.
import { calculateSequentialPathInfo } from 'faster-motion';
const pathLengths = [
{ id: 'path1', length: 100 },
{ id: 'path2', length: 150 },
{ id: 'path3', length: 50 }
];
const pathInfos = calculateSequentialPathInfo(pathLengths);
// Returns:
// [
// { id: 'path1', length: 100, startOffset: 0 },
// { id: 'path2', length: 150, startOffset: 0.333 },
// { id: 'path3', length: 50, startOffset: 0.833 }
// ]calculateSequentialTrimValues
Distribute global trim values to individual paths.
import { calculateSequentialTrimValues } from 'faster-motion';
const trimValues = calculateSequentialTrimValues(
0.25, // globalStart
0.75, // globalEnd
0, // globalOffset
pathInfos
);
// Returns Map<string, { localStart: number, localEnd: number }>
trimValues.get('path1'); // { localStart: 0.75, localEnd: 1.0 }
trimValues.get('path2'); // { localStart: 0, localEnd: 0.83 }
trimValues.get('path3'); // { localStart: 0, localEnd: 0 }calculateLocalTrim
Calculate local trim for a single path segment.
import { calculateLocalTrim } from 'faster-motion';
const { localStart, localEnd } = calculateLocalTrim(
0.25, // globalStart
0.75, // globalEnd
0.333, // pathStartOffset
0.833 // pathEndOffset
);Performance Tips
Pre-prepare Elements
For frequently animated paths, prepare them once:
// On page load
document.querySelectorAll('.animated-path').forEach(el => {
TrimPath.prepare(el);
});
// Later, animate without setup overhead
TrimPath.to('.animated-path', { end: 100, duration: 500 });Cache Path Lengths
For sequential mode with many paths:
// Calculate once
const pathInfos = calculateSequentialPathInfo(
paths.map(p => ({ id: p.id, length: p.getTotalLength() }))
);
// Reuse for multiple animations
function animateSequential(endProgress) {
const values = calculateSequentialTrimValues(0, endProgress, 0, pathInfos);
// Apply values...
}Avoid Re-measuring
Don't call getTotalLength() every frame - it's expensive:
// Bad - measures every update
TrimPath.to('#path', {
onUpdate: () => {
const length = path.getTotalLength(); // Expensive!
}
});
// Good - measure once
const length = path.getTotalLength();
TrimPath.to('#path', {
onUpdate: (state) => {
const visibleLength = (state.end - state.start) * length;
}
});Common Patterns
Sequential Reveal of Logo Parts
const logoPaths = document.querySelectorAll('.logo-path');
const pathInfos = calculateSequentialPathInfo(
Array.from(logoPaths).map((p, i) => ({
id: `path-${i}`,
length: p.getTotalLength()
}))
);
// Animate as one continuous drawing
const proxy = { progress: 0 };
FasterMotion.dom({
target: proxy,
to: { progress: 1 },
duration: 3000,
onUpdate: () => {
const values = calculateSequentialTrimValues(0, proxy.progress, 0, pathInfos);
logoPaths.forEach((p, i) => {
const trim = values.get(`path-${i}`);
// Apply trim values to path
});
}
});Infinite Loading Animation
TrimPath.to('#loader', {
start: 0,
end: 30,
offset: 720, // Two full rotations
duration: 2000,
ease: 'linear',
repeat: -1
});TypeScript Support
import {
TrimPath,
calculateSequentialPathInfo,
calculateSequentialTrimValues,
SequentialPathInfo,
LocalTrimResult
} from 'faster-motion';
const pathInfos: SequentialPathInfo[] = calculateSequentialPathInfo([
{ id: 'p1', length: 100 },
{ id: 'p2', length: 200 }
]);
const values: Map<string, LocalTrimResult> = calculateSequentialTrimValues(
0, 0.5, 0, pathInfos
);See Also
- TrimPath - Basic TrimPath usage
- Canvas TrimPath - Canvas/fmtion reference
- Timeline - Orchestrating complex animations