@pariharshyamu/morph
v2.0.1
Published
Interrupt-safe DOM morphing with spring physics, FLIP animations, Hungarian algorithm matching, and auto-animate via MutationObserver. Works with htmx, Alpine.js, Turbo, or vanilla JS.
Downloads
192
Maintainers
Readme
@pariharshyamu/morph
Interrupt-safe DOM morphing with spring physics, FLIP animations, and auto-animate.
morph.js v2 patches your DOM like morphdom, but adds silky smooth animations — spring physics, FLIP transitions, choreographed enter/exit/move, and mid-animation interruption handling.
Features
- ✦ Interrupted animation handling — morphs from current visual position, never snaps
- ✦ Scroll-aware FLIP — corrects
getBoundingClientRectacross scrolled containers - ✦ Stacking-context correction — handles CSS transform ancestors
- ✦ Hungarian algorithm matching — optimal global assignment, not greedy
- ✦ Spring physics engine — position/velocity integrator, replaces cubic-bezier
- ✦ Simultaneous move + content morph — FLIP + crossfade in one animation track
- ✦ Choreography model — sequence exits, moves, enters independently
- ✦ MorphObserver — auto-animate any DOM mutations on a container
- ✦ htmx extension — hooks into htmx:beforeSwap / htmx:afterSwap
- ✦ Confidence-gated matching — low-confidence pairs become enter/exit instead
Install
npm install @pariharshyamu/morphOr use a CDN:
<script type="module">
import morph from 'https://unpkg.com/@pariharshyamu/morph';
</script>Quick Start
import morph from '@pariharshyamu/morph';
// Morph an element to new HTML
const result = morph(element, '<div class="card">New content</div>');
// Wait for all animations to complete
await result.finished;API
morph(fromEl, toElOrHTML, options?)
Patches fromEl to match toElOrHTML (a DOM element or HTML string), animating the transition.
Returns: { animations, finished, debug, cancel() }
Options
| Option | Default | Description |
|--------|---------|-------------|
| duration | 400 | Base duration in ms |
| easing | 'spring' | 'spring' or any CSS easing string |
| spring | { stiffness: 280, damping: 24, mass: 1 } | Spring physics parameters |
| stagger | 25 | Stagger delay between elements (ms) |
| keyAttribute | 'data-morph-key' | Attribute for explicit element matching |
| matchConfidenceThreshold | 1.5 | Below this → treat as enter/exit |
| respectReducedMotion | true | Skip animation if user prefers reduced motion |
| morphContent | true | Cross-fade content changes during move |
| debug | false | Log matching info to console |
morph.text(el, newText, options?)
Cross-fade text content change with a fade-out → swap → fade-in sequence.
await morph.text(heading, 'New Title').finished;morph.prepare(fromEl, toEl, options?)
Create a controllable transition that can be triggered later.
const transition = morph.prepare(el, newEl, { duration: 500 });
transition.play(); // triggers when readyMorphObserver
Auto-animate any DOM mutations on a container:
import { MorphObserver } from '@pariharshyamu/morph';
const observer = new MorphObserver(container, { duration: 350 });
observer.observe();
// Now any mutation to container's children will auto-animate
container.innerHTML = '<div>New content</div>'; // animated!
observer.disconnect();htmx Extension
<script src="https://unpkg.com/htmx.org"></script>
<script type="module">
import morph from '@pariharshyamu/morph';
morph.htmx({ duration: 350 });
</script>
<div hx-ext="morph" hx-get="/api/items" hx-swap="innerHTML">
<!-- content swaps are now animated -->
</div>Choreography
Control the timing of exit, move, and enter phases:
morph(el, newHTML, {
choreography: {
exit: { at: 0, duration: 0.5 }, // fractions of total duration
move: { at: 0.15, duration: 0.85 },
enter: { at: 0.4, duration: 0.6 },
}
});Custom Hooks
morph(el, newHTML, {
onEnter: (el) => [
{ opacity: 0, transform: 'translateX(-20px)' },
{ opacity: 1, transform: 'translateX(0)' },
],
onLeave: (el) => [
{ opacity: 1 },
{ opacity: 0, transform: 'scale(0.8)' },
],
onMove: (el, fromRect, toRect) => null, // return keyframes or null
});License
MIT
