@multitrack/core
v0.1.1
Published
A scroll-driven animation engine with multi-track timeline architecture
Maintainers
Readme
@multitrack/core
A scroll-driven animation engine with a multi-track timeline architecture, inspired by video editing software. Pure TypeScript, zero dependencies.
Install
npm install @multitrack/coreUsage
import { Timeline, ScrollDriver } from "@multitrack/core";
const timeline = new Timeline({
config: [
{ name: "intro", duration: 3, track: "main", easing: "linear" },
{ name: "feature", duration: 5, track: "main" },
{ name: "outro", duration: 3, track: "main", easing: "linear" },
// Independent text track — overlaps freely with main
{ name: "buffer", duration: 4, track: "text" },
{ name: "caption", duration: 3, track: "text" },
],
});
// Connect to scroll position
const driver = new ScrollDriver(timeline, {
container: document.querySelector("#scroll-container"),
});
// React to step changes
timeline.on("step:enter", ({ name }) => console.log("entered", name));
timeline.on("step:exit", ({ name }) => console.log("exited", name));
// Read opacity values at any scroll progress
const opacities = timeline.getOpacities();
// { intro: 0.8, feature: 0, outro: 0, buffer: 1, caption: 0 }API
Timeline
The core engine. Resolves step configs into absolute positions and calculates opacity values at any scroll progress.
const timeline = new Timeline({ config, breakpoints?, devtools? });
timeline.setProgress(0.5); // Set playhead position (0–1)
timeline.getOpacities(); // Get all step opacities
timeline.getCurrentSteps(); // Get currently active steps
timeline.on("step:enter", handler); // Listen to events
timeline.use(middleware); // Add middleware
timeline.scope(() => { ... }); // Scoped subscriptions
timeline.dispose(); // Clean upScrollDriver
Connects a Timeline to a scrollable DOM element, translating scroll position into timeline progress.
Easings
Built-in presets: snap (default — binary 0/1), linear, easeIn, easeOut, easeInOut. Or pass a custom function.
{ name: "fade", duration: 3, track: "main", easing: "linear" }
{ name: "custom", duration: 4, track: "main", easing: (t) => t * t }Middleware
Intercept step:enter and step:exit events:
timeline.use((event, next) => {
analytics.track(event.type, event.payload.name);
next();
});Responsive tracks
Include or exclude steps based on named breakpoints tied to CSS media queries:
const timeline = new Timeline({
config: [
{ name: "mobile-hero", duration: 5, track: "main", when: "mobile" },
{ name: "desktop-hero", duration: 8, track: "main", when: "desktop" },
],
breakpoints: {
mobile: "(max-width: 767px)",
desktop: "(min-width: 768px)",
},
});Scope cleanup
Collect subscriptions into a scope and dispose them all at once — similar to GSAP's gsap.context().
const ctx = timeline.scope(() => {
timeline.on("step:enter", handleEnter);
timeline.on("scroll", handleScroll);
timeline.use(loggingMiddleware);
});
// later: clean up everything at once
ctx.dispose();Conditional steps
For runtime conditions beyond media queries, use the condition predicate. Steps where condition returns false are excluded from the resolved timeline.
{ name: "mobile-hero", duration: 5, track: "main", condition: () => window.innerWidth < 768 }Dev-mode warnings
In development, the engine validates your config and warns about common mistakes:
- Zero or negative duration steps
- Using
snapeasing on long steps (likely unintended) - Lone tracks that might be typos
whenreferences to undefined breakpoints
React bindings
See @multitrack/react for React hooks and components.
License
MIT
