Motion() Factory

Complete reference for the Motion() function — retrieve, create, and configure timelines.

Motion() is the single entry point for every animation in the SDK. Call it to create a named timeline, retrieve an existing one, or build a multi-step sequence — then chain a trigger to activate it.


Import

typescript
import { Motion } from "@motion.page/sdk";

No-bundler environments can import Motion directly from the CDN:

html
<script type="module">
  import { Motion } from "https://cloud.motion.page/sdk/latest.js";

  Motion("fade", ".box", { from: { opacity: 0 }, duration: 0.6 }).onPageLoad();
</script>

Function Signatures

Motion has three call signatures:

typescript
// 1. Retrieve an existing timeline by name
Motion(name: string): Timeline

// 2. Single-animation timeline — one target, one config object
Motion(name: string, target: TargetInput, config: AnimationConfig): Timeline

// 3. Multi-step timeline — array of animation entries
Motion(name: string, animations: AnimationEntry[]): Timeline

The name is a registry key. The same name always returns the same Timeline instance, anywhere in your code. Passing target and config with an already-registered name appends to that timeline rather than replacing it — see Appending Warning.


Basic Usage

typescript
import { Motion } from "@motion.page/sdk";

// Create and play immediately
Motion("hero", "#hero", {
  from: { opacity: 0, y: 40 },
  duration: 0.8,
  ease: "power2.out",
}).play();

// Create and play on page load
Motion("cards", ".card", {
  from: { opacity: 0, y: 30 },
  duration: 0.6,
  stagger: 0.08,
  ease: "power2.out",
}).onPageLoad();

// Retrieve an existing timeline later by name
const tl = Motion("hero");
tl.pause();


TargetInput

The target parameter (overload 2) and the target field in each AnimationEntry (overload 3) accept a TargetInput:

Type Example
CSS selector string "#hero", ".card", "[data-animate]"
string[] ["#title", "#subtitle"]
Element document.querySelector(".box")
NodeList document.querySelectorAll(".item")
Element[] Array of DOM elements
Plain object { value: 0 } — for non-DOM tweening
Array of plain objects [{ count: 0 }, { count: 100 }]

CSS selectors are the most common form. Plain objects are useful for tweening arbitrary JS values — for example, driving a canvas counter or animating a custom data property:

typescript
const counter = { value: 0 };

Motion("count-up", counter, {
  to: { value: 1000 },
  duration: 2,
  ease: "power2.out",
  onUpdate: () => {
    document.querySelector("#counter")!.textContent =
      Math.round(counter.value).toString();
  },
}).onPageLoad();

AnimationConfig

The config object passed to overload 2, and embedded in each AnimationEntry in overload 3.

Option Type Default Description
from AnimationVars Start values. SDK reads current CSS as to when only from is specified
to AnimationVars End values. SDK reads current CSS as from when only to is specified
duration number 0.5 Animation duration in seconds
delay number 0 Seconds to wait before the animation starts
ease string "power1.inOut" Easing curve — e.g. "power2.out", "elastic.out"
stagger number | StaggerVars Per-element delay when target matches multiple elements
repeat number | RepeatConfig Repeat count for this animation. -1 = infinite
split SplitType Split text into chars, words, or lines before animating
mask boolean false Wraps split elements in overflow: hidden containers for reveal effects
flap FlapConfig Split-flap / character-scramble effect. Requires split: 'chars'
fit FitConfig FLIP-style morph between two elements. Overrides from/to
axis 'x' | 'y' Axis binding for .onMouseMove() trigger
onStart () => void Fired when the animation begins
onUpdate (progress: number) => void Fired on every frame with current progress (0–1)
onComplete () => void Fired when the animation finishes
onRepeat (repeatCount: number) => void Fired after each repeat cycle
onReverseComplete () => void Fired when animation completes in reverse

Full TypeScript shape:

typescript
interface AnimationConfig {
  from?:              AnimationVars;
  to?:                AnimationVars;
  duration?:          number;
  delay?:             number;
  ease?:              string;
  stagger?:           number | StaggerVars;
  repeat?:            number | RepeatConfig;
  split?:             SplitType;
  mask?:              boolean;
  flap?:              FlapConfig;
  fit?:               FitConfig;
  axis?:              'x' | 'y';
  onStart?:           () => void;
  onUpdate?:          (progress: number) => void;
  onComplete?:        () => void;
  onRepeat?:          (repeatCount: number) => void;
  onReverseComplete?: () => void;
}

AnimationVars

All properties that can appear in from and to blocks.

Transforms

Property Type Notes
x number | string Translate X — pixels by default (50 = 50px)
y number | string Translate Y — pixels by default
z number | string Translate Z — pixels by default
rotate number | string Rotation in degrees (90 = 90deg)
rotateX number | string 3D rotation around X axis
rotateY number | string 3D rotation around Y axis
rotateZ number | string 3D rotation around Z axis (same as rotate)
scale number Uniform scale — unitless (1.5 = 150%)
scaleX number Horizontal scale
scaleY number Vertical scale
scaleZ number Depth scale
skewX number | string Horizontal skew in degrees
skewY number | string Vertical skew in degrees
perspective number | string CSS perspective depth
transformOrigin string Transform origin — e.g. "50% 50%", "top left", "0% 100%"

Visual

Property Type Notes
opacity number 0 (transparent) to 1 (fully visible)
backgroundColor string Any CSS color string
color string Text / foreground color
filter string CSS filter string — e.g. "blur(4px) brightness(0.8)"
clipPath string CSS clip-path shape function (camelCase form)
'clip-path' string CSS clip-path shape function (kebab-case, must be quoted)
boxShadow string CSS box-shadow string
borderRadius number | string Border radius — px or string
borderColor string Border color
outlineColor string Outline color

Layout

Property Type Notes
width number | string Element width — px or string with unit
height number | string Element height — px or string with unit
top number | string CSS top
left number | string CSS left
right number | string CSS right
bottom number | string CSS bottom
margin string Shorthand margin
padding string Shorthand padding
zIndex number Stacking order
backgroundPosition string Background position

Typography

Property Type Notes
fontSize number | string Font size
lineHeight number | string Line height
letterSpacing number | string Letter spacing

SVG

Property Type Notes
fill string SVG fill color
stroke string SVG stroke color
drawSVG string | DrawSVGObject Animate SVG stroke — see drawSVG

Motion Path

typescript
path: {
  target:   string | Element;  // CSS selector, Element, or raw path data (M/m commands)
  align?:   string | Element;  // Align bounding box to this element
  alignAt?: [number, number];  // Origin point [x%, y%]; default [50, 50]
  start?:   number;            // Path start fraction 0–1; default 0
  end?:     number;            // Path end fraction 0–1; default 1
  rotate?:  boolean;           // Auto-rotate along the path tangent
}

CSS Custom Properties

Any CSS custom property — prefix with --:

typescript
to: { '--accent-color': '#9966FF', '--spacing': '24px' }

from / to Resolution

The SDK auto-resolves the missing endpoint from the element’s computed CSS at build time. Specify only what’s different from the natural state.

Config Behavior
from only SDK reads current CSS as to. Start at custom values, animate into natural state — use for reveals
to only SDK reads current CSS as from. Start at natural state, animate to custom values — use for hover/exit effects
Both Both endpoints explicit — use when neither endpoint matches the element’s natural CSS

Natural CSS defaults — the SDK treats these as the element’s rest state. Omit them from to when they’re the target endpoint:

opacity: 1 · x: 0 · y: 0 · z: 0 · scale: 1 · scaleX: 1 · scaleY: 1 · rotate: 0

typescript
// ✅ from-only — SDK fills in opacity:1, y:0 from the element's CSS
Motion("reveal", ".card", {
  from: { opacity: 0, y: 50 },
  duration: 0.6,
}).onPageLoad();

// ✅ to-only — start at natural state, hover to raised state
Motion("lift", ".card", {
  to: { y: -8, boxShadow: "0 12px 24px rgba(0,0,0,0.15)" },
  duration: 0.3,
}).onHover({ each: true, onLeave: "reverse" });

// ✅ both — parallax where neither endpoint is the natural state
Motion("parallax", ".bg", {
  from: { y: -60 },
  to:   { y: 60 },
}).onScroll({ scrub: true });

// ❌ redundant — opacity:1 and y:0 are natural defaults
Motion("bad", ".card", {
  from: { opacity: 0, y: 50 },
  to:   { opacity: 1, y: 0 },
}).onPageLoad();

Multi-Step Timelines

Pass an array of AnimationEntry objects as the second argument to build coordinated sequences across multiple targets.

typescript
Motion("intro", [
  { target: ".hero-title",    from: { opacity: 0, y: 40 }, duration: 0.7, ease: "power2.out" },
  { target: ".hero-subtitle", from: { opacity: 0, y: 30 }, duration: 0.6, ease: "power2.out" },
  { target: ".hero-cta",      from: { opacity: 0, y: 20 }, duration: 0.5, ease: "power2.out" },
]).onPageLoad();

By default, each step starts immediately after the previous one ends. Control timing with the position field.

AnimationEntry

Every entry extends AnimationConfig with two additional fields:

Field Type Description
target TargetInput Required. CSS selector, Element, NodeList, or Element[]
position number | string When in the timeline this step starts. Omit for sequential playback
typescript
interface AnimationEntry extends AnimationConfig {
  target:    TargetInput;
  position?: number | string;
}

Position Syntax

Used in AnimationEntry.position, tl.call(), and scroll trigger end values.

Value Meaning
0.5 Absolute — 0.5 s from the timeline start
"+=0.5" 0.5 s after the previous entry ends
"-=0.3" 0.3 s before the previous entry ends (overlap)
">" Immediately after the previous entry ends (same as default)
"<" Same start time as the previous entry (parallel)
"<0.2" 0.2 s after the start of the previous entry
">-0.1" 0.1 s before the end of the previous entry
typescript
Motion("hero", [
  // Title and image start at the same time (parallel)
  { target: ".title", from: { opacity: 0, y: 40 }, duration: 0.8, ease: "power3.out" },
  { target: ".image", from: { opacity: 0, scale: 0.9 }, duration: 0.8, ease: "power2.out", position: "<" },
  // Badge appears 0.3 s after both start
  { target: ".badge", from: { opacity: 0, scale: 0 }, duration: 0.4, ease: "back.out", position: "<0.3" },
  // Footer enters with a gap after everything else
  { target: ".footer", from: { opacity: 0 }, duration: 0.4, position: "+=0.2" },
]).onPageLoad();

Timeline Object

Every Motion() call returns a Timeline instance with the following methods.

Playback

typescript
tl.play(from?: number): this       // Play forward; optional start time in seconds
tl.pause(atTime?: number): this    // Pause; optional snap-to time
tl.reverse(from?: number): this    // Play backward; optional start time
tl.restart(): this                 // Seek to 0, restore initial CSS, play forward
tl.seek(position: number): this    // Jump to time without playing

State — Getter / Setter

Call with no argument to read; call with argument to set and return this for chaining.

typescript
tl.duration(): number
tl.progress(value?: number): number | this   // Normalized 0–1
tl.time(value?: number): number | this       // Current time in seconds
tl.timeScale(value?: number): number | this  // Speed multiplier (2 = double speed)
tl.isActive(): boolean                       // True while playing
tl.getName(): string | undefined             // Registered name

Callbacks at Positions

Insert callback functions at specific points in the timeline:

typescript
tl.call(callback: (...args: unknown[]) => void, params?: unknown[], position?: string | number): this
typescript
Motion("sequence", [
  { target: ".step-1", from: { opacity: 0 }, duration: 0.5 },
  { target: ".step-2", from: { opacity: 0 }, duration: 0.5 },
])
  .call(() => console.log("halfway"), [], 0.5)
  .call(() => console.log("done"))
  .onPageLoad();

Lifecycle Callbacks

typescript
tl.onStart(cb: () => void): this
tl.onUpdate(cb: (progress: number, time: number) => void): this
tl.onComplete(cb: () => void): this
tl.onRepeat(cb: (count: number) => void): this

Cleanup

typescript
tl.kill(clearProps?: boolean): void  // clearProps=true (default) restores initial CSS
tl.clear(): this                     // Reset and rebuild without destroying the instance

Timeline Repeat

.withRepeat() loops the entire timeline — all steps replay as a group. This is different from per-animation repeat, which loops only one individual animation.

typescript
tl.withRepeat(config: number | RepeatConfig): this
typescript
// Infinite loop
Motion("pulse", ".dot", {
  from: { scale: 0.8, opacity: 0.6 },
  to:   { scale: 1.2, opacity: 1 },
  duration: 0.6,
}).withRepeat({ times: -1, yoyo: true }).onPageLoad();

// 3 times with yoyo + delay between cycles
Motion("bounce", [
  { target: ".box-a", to: { y: -40 }, duration: 0.4, ease: "power2.out" },
  { target: ".box-b", to: { y: -40 }, duration: 0.4, ease: "power2.out" },
]).withRepeat({ times: 3, yoyo: true, delay: 0.5 }).onPageLoad();

Appending Warning

Calling Motion(name, target, config) when name already exists in the registry appends new entries to that timeline — it does not replace it.

This is intentional for building multi-step timelines across multiple calls, but it will surprise you when trying to rebuild a timeline in response to a state change.

typescript
// ❌ Wrong — appends a second animation to the existing "hero" timeline
Motion("hero", "#hero", { from: { opacity: 0 }, duration: 0.8 }).onPageLoad();
// (somewhere later)
Motion("hero", "#hero", { from: { opacity: 0 }, duration: 0.8 }).onPageLoad();

// ✅ Correct — kill first, then rebuild from scratch
Motion("hero").kill();
Motion("hero", "#hero", { from: { opacity: 0 }, duration: 0.8 }).onPageLoad();

Static Methods

Method Signature Description
Motion.set (target: TargetInput, vars: AnimationVars): void Zero-duration property apply. Processes through the full engine pipeline — values persist on DOM
Motion.get (name: string): Timeline | undefined Get a timeline by name; undefined if not registered
Motion.has (name: string): boolean Check whether a named timeline is registered
Motion.getNames (): string[] Array of all currently registered timeline names
Motion.kill (name: string): void Kill one timeline by name and restore initial CSS
Motion.killAll (): void Kill every registered timeline
Motion.reset (targets: TargetInput): void Kill animations, revert text splits, clear transform cache and inline styles
Motion.refreshScrollTriggers (): void Recalculate all scroll trigger positions after layout changes
Motion.cleanup (): void Detach active ScrollTriggers and safely remove their spacer/marker nodes; timelines remain registered
Motion.context (fn: () => void): MotionContext Create a scoped context tracking all timelines created inside fn()
Motion.utils MotionUtils Utility functions — see below

Motion.set

Motion.set immediately applies CSS properties with no animation, no timeline. Use it to set initial element states before animations run, preventing flashes of unstyled content:

typescript
// Hide before page loads — no flash
Motion.set(".card", { opacity: 0, y: 30 });

// Reveal on scroll
Motion("cards", ".card", {
  from: { opacity: 0, y: 30 },
  duration: 0.6,
  stagger: 0.08,
}).onScroll({ scrub: false, toggleActions: "play none none none" });

Motion.context

Creates a scoped context that tracks all timelines created inside the callback. Returns a MotionContext object for clean teardown and reinitialisation — essential for SPA navigation, AJAX pagination, and React effects.

typescript
const ctx = Motion.context(() => {
  Motion("hero", ".title", { from: { opacity: 0, y: 30 }, duration: 0.8 }).onPageLoad();
  Motion("cards", ".card", { from: { y: 100 }, duration: 1 }).onScroll({ scrub: true });
});

// After dynamic content change (AJAX filter, pagination, etc.)
ctx.refresh();   // kills old animations, re-runs init, re-resolves selectors

// Add more animations to the context later
ctx.add(() => {
  Motion("footer", ".footer-item", { from: { opacity: 0 }, duration: 0.4 }).onPageLoad();
});

// Full cleanup (React unmount, route change, etc.)
ctx.revert();

React pattern:

typescript
useEffect(() => {
  const ctx = Motion.context(() => {
    Motion("fade", ".box", { from: { opacity: 0 }, duration: 0.6 }).onScroll({ scrub: true });
  });
  return () => ctx.revert();
}, []);

Astro View Transitions:

typescript
let ctx: MotionContext | undefined;

document.addEventListener("astro:page-load", () => {
  ctx?.revert();
  ctx = Motion.context(() => {
    Motion("page-reveal", ".animate-in", { from: { opacity: 0, y: 20 }, duration: 0.5 }).onPageLoad();
  });
});

Motion.utils

A collection of mathematical utilities. Also importable as a named export:

typescript
import { MotionUtils } from "@motion.page/sdk";
Method Signature Description
toArray (target, scope?) → Element[] Convert selector / NodeList / HTMLCollection / Element to a flat array
clamp (min, max, value?) → number | fn Clamp value within range. Curried when value is omitted
random (min, max, snap?) → number Random number with optional snap increment
snap (snapTo, value?) → number | fn Snap to nearest increment or array value. Curried when value is omitted
interpolate (start, end, progress) → number Linear interpolation (lerp) between two values
mapRange (inMin, inMax, outMin, outMax, value?) → number | fn Map a value from one range to another. Curried when value is omitted
normalize (min, max, value?) → number | fn Normalize a value to 0–1. Curried when value is omitted
wrap (min, max, value?) → number | fn Modular wrap within a range. Curried when value is omitted
typescript
// toArray — convert selector to elements for manual iteration
const panels = Motion.utils.toArray(".panel");

// clamp — keep a value within bounds
const clamped = Motion.utils.clamp(0, 100, 150); // → 100

// Curried form — create a reusable clamp function
const clamp0to1 = Motion.utils.clamp(0, 1);
clamp0to1(1.5); // → 1

// mapRange — convert scroll progress to rotation
const progress = Motion.utils.mapRange(0, window.innerHeight, 0, 360, scrollY);

// interpolate — lerp between colors or numbers
const value = Motion.utils.interpolate(0, 100, 0.75); // → 75

// wrap — loop an index within a range
const index = Motion.utils.wrap(0, slides.length, currentIndex + 1);

Trigger Methods

Timelines are inert until a trigger is chained. Creating a timeline with Motion(name, target, config) builds the animation but does not play or attach it.

Method Description
.play() Play immediately, right now
.onPageLoad(config?) Play when DOM is ready — fires immediately if already loaded
.onHover(config?) Play on mouseenter, react on mouseleave
.onClick(config?) Play/replay on click; optionally reverse or restart
.onScroll(config?) Scrub or snap animation to scroll position
.onMouseMove(config?) Drive progress from mouse position
.onPageExit(config?) Intercept link clicks, play animation, then navigate
.onGesture(config) Map pointer / touch / wheel / scroll gestures to animation progress
.onCursor(config) Replace the native cursor with an animated custom cursor

Each trigger has its own configuration options — see the dedicated pages: Page Load · Hover · Click · Scroll Trigger · Mouse Movement · Page Exit · Gesture · Custom Cursor.

each: true — Independent Per-Element Instances

Without each, all matched elements share one timeline instance (group behaviour). With each: true, every element gets its own independent timeline.

typescript
// Group — hovering any card animates all of them
Motion("cards", ".card", { to: { y: -8 }, duration: 0.3 }).onHover({ target: ".card", onLeave: "reverse" });

// Independent — each card animates on its own hover
Motion("cards", ".card", { to: { y: -8 }, duration: 0.3 }).onHover({ each: true, onLeave: "reverse" });

each: true is supported in: .onHover(), .onClick(), .onScroll(), .onMouseMove(), .onGesture().


Stagger

stagger delays the start of each matched element’s animation, creating cascade effects.

Shorthand

Pass a number — each element waits that many seconds more than the previous:

typescript
Motion("list", ".item", {
  from: { opacity: 0, y: 20 },
  duration: 0.5,
  stagger: 0.08,
}).onPageLoad();
// Element 1 at 0s, element 2 at 0.08s, element 3 at 0.16s …

StaggerVars Object

Option Type Default Description
each number Seconds between each element
amount number Total spread across all elements (alternative to each)
from 'start' | 'end' | 'center' | 'edges' | 'random' | number 'start' Which element starts first
grid 'auto' | [number, number] Enable 2D grid staggering
axis 'x' | 'y' Axis to measure distance for grid stagger
ease string 'none' Easing for the stagger delay distribution
typescript
// Center-outward ripple on a grid
Motion("grid", ".cell", {
  from: { opacity: 0, scale: 0.6 },
  duration: 0.4,
  stagger: { each: 0.04, from: "center", grid: "auto" },
  ease: "power2.out",
}).onPageLoad();

See Stagger for the full stagger reference.


Repeat Config

The repeat option on individual animations. For repeating the whole timeline, use .withRepeat().

typescript
// Shorthand number
repeat: 3     // 3 additional cycles after the first play
repeat: -1    // Infinite

// Full config object
interface RepeatConfig {
  times:  number;    // Additional repetitions; -1 = infinite
  delay?: number;    // Seconds between each repetition
  yoyo?:  boolean;  // Alternate direction each cycle
}
typescript
// Infinite yoyo
Motion("breathe", ".logo", {
  to: { scale: 1.08 },
  duration: 1.2,
  ease: "sine.inOut",
  repeat: { times: -1, yoyo: true },
}).onPageLoad();

// 3 times with yoyo + rest delay
Motion("shake", ".error-field", {
  from: { x: -6 },
  to:   { x:  6 },
  duration: 0.06,
  ease: "none",
  repeat: { times: 7, yoyo: true, delay: 0 },
}).play();

Text Splitter

Split a text element into individual spans before animating. Each part becomes independently animatable.

split values

Value Effect
'chars' Wrap each character in a <span>
'words' Wrap each word in a <span>
'lines' Wrap each line in a <span> (layout-dependent)
'chars,words' Both chars and words wrapped
'words,lines' Both words and lines wrapped
'chars,words,lines' All three levels wrapped
typescript
Motion("headline", "h1", {
  split: "words",
  from: { opacity: 0, y: "110%" },
  duration: 0.6,
  stagger: { each: 0.06, from: "start" },
  ease: "power3.out",
}).onPageLoad();

Split elements receive data attributes for CSS targeting:

Attribute Set on Index attribute
[data-split-char] Each character span data-char-index
[data-split-word] Each word span data-word-index
[data-split-line] Each line span data-line-index
[data-split] Root element

Inline elements (e.g. <span class="accent">) are preserved during splitting.

See Text Splitter for the full text splitter reference.


Mask

mask: true wraps each split element in an overflow: hidden container. The container clips the element during animation, creating a reveal effect where text appears to rise from beneath a surface.

Requires split to be set — mask has no effect without text splitting.

typescript
// Lines slide up from behind a clip — classic editorial reveal
Motion("reveal", ".tagline", {
  split: "lines",
  mask: true,
  from: { y: "100%" },
  duration: 0.7,
  stagger: 0.1,
  ease: "power3.out",
}).onPageLoad();

The mask wrapper receives [data-split-mask] for CSS targeting.


Flap (Text Flapper)

A split-flap display effect that cycles through intermediate characters before landing on the real text. Requires split: 'chars' on the same animation.

typescript
Motion("scramble", ".tagline", {
  split: "chars",
  flap: {
    type: "flip",
    charset: "alphanumeric",
    cycles: [2, 5],
  },
  from: { opacity: 0 },
  duration: 1.2,
  stagger: 0.04,
}).onPageLoad();

FlapConfig

Option Type Default Description
type FlapType Required. Visual effect for each cycle
charset CharsetPreset | string 'alphanumeric' Characters to cycle through. Pass a string for a custom set
cycles number | [number, number] 3 Intermediate characters before landing. Tuple = random range per cell
perspective number 400 CSS perspective depth in px — applies to flip and board only
styledBoard boolean false Render decorative flip-board cell UI — applies to board only
stableWidth boolean | 'cells' | 'container' true when styledBoard Layout-shift strategy. true/'cells' pins each char cell to the widest glyph; 'container' pins only the parent element’s outer width so letters flow naturally inside
preserveWhitespaceCells boolean false Animate whitespace characters as cycling placeholder cells

FlapType values

Value Description
'flip' 3D rotateX card flip. Composable with from/to transforms
'fade' Opacity crossfade. Owns opacity — do not set opacity in from/to
'slide' Characters slide in/out vertically
'blur' Blur dissolve. Owns filter — do not set filter in from/to
'scale' Characters scale in/out
'board' Decorative split-flap board cells — use with styledBoard: true
'none' No built-in style — run cycles only, drive visuals entirely via CSS

CharsetPreset values

Value Characters
'alphanumeric' A–Z, 0–9
'alpha' A–Z only
'numeric' 0–9 only
'symbols' Punctuation and special characters
'binary' 0 and 1 only
'hex' 0–9, A–F
'katakana' Japanese katakana characters
'braille' Braille dot patterns

See Text Flapper for the full reference.


Fit (FLIP Morphing)

Measure both elements’ bounding rects and animate the visual delta between them — a FLIP-style morph. When fit is set, from and to are ignored.

typescript
Motion("morph", ".source", {
  fit: { target: ".destination", resize: true },
  duration: 0.5,
  ease: "power2.inOut",
}).play();

FitConfig

Option Type Default Description
target string Required. CSS selector for the destination element
absolute boolean false Convert source to absolute positioning during animation
scale boolean true Use scaleX/scaleY — GPU-accelerated but distorts text and borders
resize boolean false Animate actual width/height — no distortion, triggers layout reflow. Mutually exclusive with scale

See Fit Animation for the full reference.


drawSVG

Animate the visible portion of an SVG stroke. The element must have a stroke — the SDK auto-computes stroke-dasharray if not set.

Input Meaning
"0% 100%" Full stroke visible
"0% 0%" Fully hidden (start state for draw-in)
"20% 80%" Middle portion only
"50%" Shorthand for "0% 50%"
"100px 500px" Pixel range along the stroke length
"true" Full stroke (keyword)
"false" / "none" Hidden
{ start: 20, end: 80 } Object — values are percentages 0–100 (not 0–1)
typescript
// Draw a path from left to right
Motion("draw", "path.line", {
  from: { drawSVG: "0% 0%" },
  to:   { drawSVG: "0% 100%" },
  duration: 1.2,
  ease: "power2.inOut",
}).onScroll({ scrub: true });

Object form: { start: 20, end: 80 } means 20% to 80% — values are percentages 0–100, not fractions 0–1.

See SVG Animations for the full drawSVG reference.


clip-path

Animate the CSS clip-path property between matching shape functions. Both clipPath (camelCase) and 'clip-path' (kebab-case, quoted) keys are accepted. The renderer writes both clip-path and -webkit-clip-path automatically.

Shape Example
circle 'circle(50% at 50% 50%)'
ellipse 'ellipse(60% 30% at 50% 50%)'
inset 'inset(10px 20px round 8px)'
polygon 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)'
rect 'rect(10px 90px 90px 10px)'
xywh 'xywh(10% 10% 80% 80%)'

Interpolation rules:

  • Same shape on both ends for smooth interpolation. Cross-shape (e.g. circle → inset) hard-swaps at progress ≥ 0.5.
  • polygon vertex counts must match between from and to for smooth morphing.
  • Units are preserved (%, px).
typescript
// Circular reveal — expand from center
Motion("reveal", "#hero", {
  from: { 'clip-path': 'circle(0% at 50% 50%)' },
  to:   { 'clip-path': 'circle(75% at 50% 50%)' },
  duration: 1,
  ease: "power2.out",
}).onPageLoad();

// Diagonal wipe on hover
Motion("wipe", ".card-image", {
  from: { clipPath: 'polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)' },
  to:   { clipPath: 'polygon(0% 0%, 0% 0%, 100% 100%, 0% 100%)' },
  duration: 0.5,
  ease: "power2.inOut",
}).onHover({ onLeave: "reverse" });

See Clip Path for the full reference.


Easing Reference

The ease string follows the format "family.direction" — lowercase, e.g. "power2.out". Unknown strings fall back to "power1.out". Easing names are case-insensitive.

Family Variants Best for
power1 .in · .out · .inOut Subtle UI motion, understated transitions
power2 .in · .out · .inOut Scroll reveals, card entrances — the everyday workhorse
power3 .in · .out · .inOut Hero sections, modals, bold entrances
power4 .in · .out · .inOut Cinematic moments, high-impact exits
sine .in · .out · .inOut Delicate fades, floating loops
expo .in · .out · .inOut Drawers, command palettes, snap-open UI
circ .in · .out · .inOut Sorting animations, mechanical / precision UI
back .in · .out · .inOut Playful cards, buttons — mild overshoot
elastic .in · .out · .inOut Badges, tooltips, success states — spring energy
bounce .in · .out · .inOut Game UI, mascots — discrete ball-drop bounces
rough Glitch effects, error shakes, organic feel
slow .in · .out · .inOut Dramatic pauses, attention-drawing loops
none Scrub animations, progress bars — constant speed

Omitting ease uses the SDK default: equivalent to "power1.inOut".

See Easing Functions for visual comparison and usage guidance.


Callbacks

Lifecycle callbacks can be defined inline in AnimationConfig or chained on the Timeline:

typescript
// Inline in config
Motion("reveal", ".box", {
  from: { opacity: 0, y: 30 },
  duration: 0.6,
  onStart:           () => console.log("started"),
  onUpdate:          (progress) => console.log(`progress: ${progress.toFixed(2)}`),
  onComplete:        () => console.log("done"),
  onRepeat:          (count) => console.log(`repeat #${count}`),
  onReverseComplete: () => console.log("reversed"),
}).onPageLoad();

// Chained on the timeline
Motion("counter", "#count", {
  from: { opacity: 0 },
  duration: 0.4,
})
  .onStart(() => startTracking())
  .onUpdate((progress, time) => updateProgress(progress))
  .onComplete(() => finishTracking())
  .play();
Callback Signature Fires when
onStart () => void Animation begins playing
onUpdate (progress: number, time: number) => void Every animation frame
onComplete () => void Animation reaches the end
onRepeat (count: number) => void After each repeat cycle
onReverseComplete () => void Animation completes while playing in reverse

Dynamic Content & SPA

Use Motion.context() to scope animations for clean teardown and reinitialisation when DOM content changes — AJAX filters, pagination, SPA navigation, infinite scroll.

typescript
// Create a context — all timelines created inside are tracked
const ctx = Motion.context(() => {
  Motion("hero", ".title", { from: { opacity: 0, y: 30 }, duration: 0.8 }).onPageLoad();
  Motion("cards", ".card", { from: { y: 100 }, duration: 1 }).onScroll({ scrub: true });
});

// After dynamic content change (filter, pagination, etc.)
ctx.refresh();  // kills old animations, re-runs init, re-resolves all selectors

// Add more animations to the same context
ctx.add(() => {
  Motion("new-section", ".new-item", { from: { opacity: 0 }, duration: 0.4 }).onPageLoad();
});

// Full cleanup — React unmount, route change, etc.
ctx.revert();

Both published targets — the WordPress plugin and standalone / CDN exports — wrap your page’s animation code in Motion.context() automatically and expose MOTIONPAGE_FRONT.reinit(). Call it after AJAX or other dynamic content changes to silently re-initialise animations (including on newly added elements):

js
MOTIONPAGE_FRONT.reinit();

See SPA Integration for framework-specific patterns (React, Vue, Next.js, Astro).


Anti-Patterns

DON’T DO instead
from: { opacity: 0, y: 50 }, to: { opacity: 1, y: 0 } from: { opacity: 0, y: 50 }opacity:1 and y:0 are natural defaults
import { Motion } from '@motion/sdk' import { Motion } from '@motion.page/sdk'
Call Motion("name", ...) twice expecting replacement Call Motion("name").kill() first, then rebuild
Target multiple independent elements without each: true Add each: true to the trigger config
querySelectorAll(".card").forEach(el => Motion(..., el, ...)) Motion("cards", ".card", {...}).onHover({ each: true })
Mix external animation libraries with Motion SDK Use Motion SDK exclusively
drawSVG: { start: 0.2, end: 0.8 } expecting fractions Object values are percentages 0–100: { start: 20, end: 80 }
Use playNext/playPrevious without each: true Always combine playNext/playPrevious actions with each: true
Hover/HoverEnd gesture events on window Always specify a target element for hover events
Set flap.type: 'fade' and also animate opacity fade type owns opacity — drop opacity from from/to

TypeScript Types

All types are importable from @motion.page/sdk:

typescript
import type {
  // Core
  AnimationVars,
  AnimationConfig,
  AnimationEntry,
  TargetInput,
  ObjectTarget,
  AnimationTarget,
  EasingFunction,

  // Config options
  StaggerVars,
  RepeatConfig,
  SplitType,
  PathConfig,
  FitConfig,
  FlapConfig,

  // Trigger configs
  HoverConfig,
  ClickConfig,
  ScrollConfig,
  MouseMoveConfig,
  MarkerConfig,
  PageLoadConfig,
  PageExitConfig,

  // Gesture
  GestureConfig,
  GestureEvent,
  GestureAction,
  GestureInputType,
  TimelineAction,

  // Cursor
  CursorConfig,
  CursorStateVars,
  CursorSqueezeConfig,

  // Context
  MotionContext,
} from "@motion.page/sdk";

// Namespace import — all types under Types.*
import { Types } from "@motion.page/sdk";

Browser Support

Chrome 90+ · Firefox 90+ · Safari 15+ · Edge 90+


Related: Core Concepts · Getting Started · Sequencing · Stagger · Easing · SPA Integration