Fit Animation
Morph one element's position and size to match another using FLIP-style animations.
Fit animation morphs one element’s geometry — position and size — to match another element’s. The SDK measures both elements’ bounding rects at play time and animates the visual delta between them. When fit is set, from and to are ignored.
Basic Usage
Add a fit object to your animation config. Set target to a CSS selector pointing to the element you want to morph toward.
import { Motion } from "@motion.page/sdk";
Motion("morph", ".card", {
fit: { target: ".modal" },
duration: 0.6,
ease: "power3.inOut",
}).onClick({ toggle: "reverse" }); The source element (.card) animates toward the position and size of the target element (.modal). Geometry is captured at play time using getBoundingClientRect(), so the page can reflow between setup and play without stale measurements.
fit Options
| Option | Type | Default | Description |
|---|---|---|---|
target | string | — | Required. CSS selector for the element to morph toward. Measured at play time. |
scale | boolean | true | Include scaleX/scaleY transforms. Disable for position-only movement. |
resize | boolean | false | Animate width/height instead of scale. No content distortion. Mutually exclusive with scale. |
absolute | boolean | false | Animate CSS left/top/width/height. Sets position: absolute on the source element. |
Animation Modes
Fit supports three modes depending on which CSS properties you want animated.
Transform Mode (default)
Animates x, y, scaleX, and scaleY CSS transforms. GPU-accelerated and the most performant option. The trade-off: scaling can visually distort text, borders, and box-shadows at intermediate frames.
Motion("morph", ".card", {
fit: { target: ".modal" },
duration: 0.6,
ease: "power3.inOut",
}).onClick({ toggle: "reverse" }); Resize Mode
Set resize: true to animate x, y, width, and height instead of scale transforms. The element physically changes size — no distortion at any point — but it triggers layout reflow on every frame. Mutually exclusive with scale.
Motion("expand", ".card", {
fit: { target: ".modal", resize: true },
duration: 0.5,
ease: "power2.inOut",
}).onClick({ toggle: "reverse" }); Use resize mode for text-heavy cards or elements with prominent borders where scale distortion would be noticeable.
Absolute Mode
Set absolute: true to animate CSS left, top, width, and height. The SDK sets position: absolute on the source element before animating. This pulls the element out of document flow, preventing it from pushing surrounding content during the animation.
Motion("layout", ".panel", {
fit: { target: ".expanded", absolute: true },
duration: 0.5,
ease: "power2.inOut",
}).onClick({ toggle: "reverse" }); Note: In the current version,
absolute: truepermanently setsposition: absoluteon the element — it is not reverted after the animation completes.
Position Only (No Size Change)
Set scale: false to animate position without changing size. The source element slides to the target’s center with no scaling.
Motion("slide", ".indicator", {
fit: { target: ".active-tab", scale: false },
duration: 0.3,
ease: "power2.out",
}).onClick({ toggle: "reverse" }); Common Patterns
Tab Indicator
A classic active-state indicator that slides and resizes to match the active tab.
const tabs = document.querySelectorAll(".tab");
tabs.forEach((tab) => {
tab.addEventListener("click", () => {
Motion("tab-indicator", ".indicator", {
fit: { target: tab as HTMLElement, resize: true },
duration: 0.35,
ease: "power3.inOut",
}).play();
});
}); Card Expand to Modal
A card that morphs into a full-screen overlay on click. Use resize: true to avoid distorting the card’s content at intermediate frames.
Motion("card-expand", ".card-thumbnail", {
fit: { target: ".card-fullscreen", resize: true },
duration: 0.5,
ease: "power2.inOut",
}).onClick({ target: ".card-thumbnail" }); Grid Reorder with Stagger
Animate multiple grid items toward their new slot positions with a stagger.
Motion("grid-reorder", ".grid-item", {
fit: { target: ".grid-slot" },
stagger: 0.08,
duration: 0.5,
ease: "power2.inOut",
}).onClick({ target: ".reorder-btn", toggle: "reverse" }); Shared Element Transition
Simulate a shared-element transition between two views — the small thumbnail morphs toward the large detail view.
// Trigger when navigating to the detail view
Motion("shared-element", ".thumbnail", {
fit: { target: ".detail-hero", resize: true },
duration: 0.6,
ease: "expo.inOut",
}).play(); Limitations
- SVG not supported —
fitonly works with HTML elements. Passing an SVG element as source or target logs a[Motion Fit]warning and the animation is skipped. fromandtoare ignored — whenfitis set, anyfromortoproperties in the config are discarded. Use a multi-step timeline if you need to combine fit with other property changes.resizeandscaleare mutually exclusive — setting both has no defined behavior. Use one or the other.absoluteside effect —absolute: truepermanently changespositiontoabsoluteon the source element, which can affect surrounding layout.
API Reference
interface FitConfig {
/** CSS selector for the target element to morph toward. Measured at play time via getBoundingClientRect(). */
target: string;
/** Include scaleX/scaleY transforms. Default: true. May distort text, borders, and shadows. */
scale?: boolean;
/** Animate width/height instead of scale. No distortion, but triggers layout reflow. Mutually exclusive with scale. Default: false. */
resize?: boolean;
/** Use position: absolute during animation. Animates left/top/width/height. Prevents layout shifts. Default: false. */
absolute?: boolean;
}
interface AnimationConfig {
// When set, `from` and `to` are ignored
fit?: FitConfig;
} Related: Scale · Translate · Dimensions · Stagger