Custom adapters
Teach MotionPR about another animation engine with the MotionprAdapter contract.
MotionPR sees CSS animations, CSS transitions and Web Animations natively, and GSAP, Anime.js and
Lenis through its first-party adapters. For any other engine (a custom tween library, a canvas
renderer) you can write an adapter. The first-party adapters in packages/adapters/ are the
reference implementations: gsap for replayable tweens and timelines, animejs for
instrumenting an engine at creation time, lenis for scroll motion.
Status: the adapter interface is versioned (PROTOCOL_VERSION = 1) but still alpha; it may
change before 1.0.
Interface
An adapter is an object that implements MotionprAdapter and registers itself with the in-page
agent:
import type { MotionprAdapter, AdapterAnimation } from "@erenthedeveloper0/motionpr";
const adapter: MotionprAdapter = {
name: "my-engine",
beginWindow() {
/* a checkpoint just finished: forget animations that already ended */
},
collect(): AdapterAnimation[] {
return runningAnimations.map((a) => ({
id: a.id,
name: a.label ?? null,
target: a.element, // one entry per animated element
startTime: a.startedAt, // performance.now() of local time zero (before delay)
duration: a.duration, // ms per iteration
delay: a.delay,
iterations: a.repeat ?? 1, // Infinity for endless
direction: "normal",
playbackRate: 1,
easing: { value: a.easeName, samples: sampleEase(a.ease) }, // 65 samples of 0..1
properties: ["transform", "opacity"],
playState: a.done ? "finished" : "running",
samplable: true, // true if seek() can render it
}));
},
isAnimating: () => runningAnimations.some((a) => !a.done),
beginReplay() {
/* pause everything and remember state */
},
seek(timeMs) {
/* synchronously render every collected animation at performance-clock time timeMs */
},
endReplay() {
/* restore the state saved in beginReplay */
},
};
window.__MOTIONPR_AGENT__?.registerAdapter(adapter);Optional members
| Member | Purpose |
|---|---|
version, supportedRange | Engine version and the range you test against (shown in captures and doctor) |
capabilities | What the adapter can do, for diagnostics |
beginWindow() | A checkpoint finished: forget animations that already ended |
isAnimating() | true while a tracked finite animation plays (used for settling) |
beginReplay(), seek(t), endReplay() | Deterministic replay; without them records are metadata only |
collectTimelines() | TimelineRecords: duration, labels, children |
collectScroll(), scrollStates(), scrollTo() | Scroll motion, settle detection, and the scrollTo step |
claims(animation) | Mark native Animation objects your engine created, so they are recorded once |
diagnostics() | Health messages shown in captures and reports |
The agent also has unregisterAdapter(adapter); call it from your own dispose().
Rules that keep captures trustworthy
- Report times on the
performance.now()clock. seek()must render synchronously and must not fire your engine's callbacks.endReplay()must restore exactly whatbeginReplay()changed.- If something cannot be observed precisely, add a sentence to
notes; the record is then marked partial in reports instead of looking complete. - Do nothing when
window.__MOTIONPR_AGENT__is absent (production).
Loading the adapter
Either import it in your app (like @erenthedeveloper0/motionpr/gsap), or let MotionPR inject it before your page
scripts:
adapters: [{ name: "my-engine", script: "./motion/my-engine-adapter.js", required: true }],required: true emits an error diagnostic if the adapter did not register in a captured page.