Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
17dbbe7
Fixing React 19 cleanup
mattgperry Sep 24, 2025
c053920
fix: Ensure useMotionValueEvent animationComplete fires for useSpring
rick-hup Nov 1, 2025
898e6d2
refactor: Notify animationStart and animationComplete events in value…
rick-hup Nov 3, 2025
221e94e
Merge pull request #3400 from rick-hup/main
mattgperry Jan 6, 2026
800df22
Updating changelog
mattgperry Jan 6, 2026
bd225bf
v12.24.3
mattgperry Jan 6, 2026
1faabb6
Merge branch 'main' into fix/react-19-cleanup
mattgperry Jan 6, 2026
3816d85
Merge pull request #3381 from motiondivision/fix/react-19-cleanup
mattgperry Jan 6, 2026
ec5bfc0
Updating changelog
mattgperry Jan 6, 2026
c689389
v12.24.4
mattgperry Jan 6, 2026
464b238
Fix React 19 ref compatibility in AnimatePresence popLayout mode
mattgperry Jan 6, 2026
264b1a3
Merge pull request #3452 from motiondivision/popchild-refs
mattgperry Jan 6, 2026
3ada3b3
Updating changelog
mattgperry Jan 6, 2026
6e1ed5e
v12.24.5
mattgperry Jan 6, 2026
49a96c7
fix: optimize prefers-reduced-motion listener initialization
mattgperry Jan 6, 2026
40d6eb4
test: add unit tests for reduced motion listener initialization
mattgperry Jan 6, 2026
4797be1
Merge pull request #3453 from motiondivision/memory-leak
mattgperry Jan 6, 2026
d8b9a05
Adding performance benchmark for atomic updates
mattgperry Jan 6, 2026
85d3d43
Updating changelog
mattgperry Jan 6, 2026
f3adb2c
v12.24.6
mattgperry Jan 6, 2026
aa8b46b
Fix duplicate exit animation processing in AnimatePresence
mattgperry Jan 6, 2026
9cddabf
Merge pull request #3454 from motiondivision/duplicate-animate-presence
mattgperry Jan 6, 2026
f77087f
Updating changelog
mattgperry Jan 6, 2026
e82a97c
v12.24.7
mattgperry Jan 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,36 @@ Motion adheres to [Semantic Versioning](http://semver.org/).

Undocumented APIs should be considered internal and may change without warning.

## [12.24.7] 2026-01-06

### Fixed

- Fix duplicate exit animations in `AnimatePresence`.

## [12.24.6] 2026-01-06

### Fixed

- Remove memory leak from retained `matchMedia` callbacks.

## [12.24.5] 2026-01-06

### Fixed

- Prevent `ref` access error for `AnimatePresence` `popLayout` mode in React 19.

## [12.24.4] 2026-01-06

### Fixed

- Support `ref` functions that return a cleanup callback.

## [12.24.3] 2026-01-06

### Fixed

- Ensure `onAnimationStart` and `onAnimationComplete` callbacks fire for `useSpring`.

## [12.24.2] 2026-01-06

### Fixed
Expand Down
8 changes: 4 additions & 4 deletions dev/html/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "html-env",
"private": true,
"version": "12.24.2",
"version": "12.24.7",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -10,9 +10,9 @@
"preview": "vite preview"
},
"dependencies": {
"framer-motion": "^12.24.2",
"motion": "^12.24.2",
"motion-dom": "^12.24.0",
"framer-motion": "^12.24.7",
"motion": "^12.24.7",
"motion-dom": "^12.24.3",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
185 changes: 185 additions & 0 deletions dev/html/public/benchmarks/transform-rebuild-overhead.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<html>
<!--
Transform Rebuild Overhead Benchmark

This benchmark measures the overhead of rebuilding transforms when
animating non-transform properties. In the old renderer, animating
backgroundColor would trigger a full transform string rebuild.
With atomic updates, only backgroundColor would be updated.

Phase 1: Animate x, y, scale, rotate, backgroundColor on 200 divs
Phase 2 (after 1s): Animate only backgroundColor

In the old renderer, Phase 2 still rebuilds transform strings.
With atomic updates, Phase 2 only touches backgroundColor.
-->
<head>
<style>
body {
padding: 0;
margin: 0;
font-family: system-ui, sans-serif;
}

.stats {
position: fixed;
top: 10px;
left: 10px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 15px;
border-radius: 8px;
font-size: 14px;
z-index: 1000;
}

.stats div {
margin: 5px 0;
}

.container {
padding: 20px;
width: 100%;
display: flex;
flex-wrap: wrap;
gap: 5px;
}

.box {
width: 40px;
height: 40px;
background-color: #ff0000;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="stats">
<div>Phase: <span id="phase">Initializing...</span></div>
<div>Boxes: <span id="boxCount">0</span></div>
<div>FPS: <span id="fps">--</span></div>
<div>Avg Frame Time: <span id="frameTime">--</span>ms</div>
</div>
<div class="container"></div>
<script type="module" src="/src/imports/framer-motion-dom.js"></script>
<script type="module">
const { animate } = window.Motion

const NUM_BOXES = 800
const PHASE1_DURATION = 10000 // 1 second
const PHASE2_DURATION = 20000 // 2 seconds

// Create boxes
let html = ``
for (let i = 0; i < NUM_BOXES; i++) {
html += `<div class="box"></div>`
}
document.querySelector(".container").innerHTML = html
const boxes = document.querySelectorAll(".box")
document.getElementById("boxCount").textContent = NUM_BOXES

// FPS tracking
let frameCount = 0
let frameTimes = []
let lastTime = performance.now()

function trackFrame() {
const now = performance.now()
const delta = now - lastTime
lastTime = now
frameCount++
frameTimes.push(delta)

// Keep last 60 frames
if (frameTimes.length > 60) {
frameTimes.shift()
}

// Update stats every 10 frames
if (frameCount % 10 === 0) {
const avgFrameTime =
frameTimes.reduce((a, b) => a + b, 0) /
frameTimes.length
const fps = 1000 / avgFrameTime
document.getElementById("fps").textContent = fps.toFixed(1)
document.getElementById("frameTime").textContent =
avgFrameTime.toFixed(2)
}

requestAnimationFrame(trackFrame)
}

// Start FPS tracking
requestAnimationFrame(trackFrame)

// Phase 1: Animate all properties
document.getElementById("phase").textContent =
"Phase 1: All properties (x, y, scale, rotate, backgroundColor)"

const phase1Animations = []
boxes.forEach((box, i) => {
// Stagger the animations slightly for visual interest
const delay = (i % 20) * 10

phase1Animations.push(
animate(
box,
{
x: [0, 50, 0],
y: [0, 30, 0],
scale: [1, 1.2, 1],
rotate: [0, 180, 360],
backgroundColor: [
"#ff0000",
"#00ff00",
"#0000ff",
"#ff0000",
],
},
{
duration: PHASE1_DURATION / 1000,
delay: delay / 1000,
ease: "easeInOut",
}
)
)
})

// Phase 2: After 1 second, animate ONLY backgroundColor
// This is where the overhead difference shows:
// - Old renderer: rebuilds transform string on every backgroundColor change
// - Atomic updates: only updates backgroundColor style property
setTimeout(() => {
document.getElementById("phase").textContent =
"Phase 2: Only backgroundColor (transform overhead test)"

// Reset frame tracking for phase 2
frameTimes = []
frameCount = 0

boxes.forEach((box, i) => {
const delay = (i % 20) * 5

animate(
box,
{
backgroundColor: [
"#0000ff",
"#ff00ff",
"#00ffff",
"#ffff00",
"#ff0000",
],
},
{
duration: PHASE2_DURATION / 1000,
delay: delay / 1000,
ease: "linear",
repeat: Infinity,
}
)
})
}, PHASE1_DURATION + 500)
</script>
</body>
</html>
4 changes: 2 additions & 2 deletions dev/next/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "next-env",
"private": true,
"version": "12.24.2",
"version": "12.24.7",
"type": "module",
"scripts": {
"dev": "next dev",
Expand All @@ -10,7 +10,7 @@
"build": "next build"
},
"dependencies": {
"motion": "^12.24.2",
"motion": "^12.24.7",
"next": "15.4.10",
"react": "19.0.0",
"react-dom": "19.0.0"
Expand Down
4 changes: 2 additions & 2 deletions dev/react-19/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "react-19-env",
"private": true,
"version": "12.24.2",
"version": "12.24.7",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -11,7 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"motion": "^12.24.2",
"motion": "^12.24.7",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
Expand Down
4 changes: 2 additions & 2 deletions dev/react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "react-env",
"private": true,
"version": "12.24.2",
"version": "12.24.7",
"type": "module",
"scripts": {
"dev": "yarn vite",
Expand All @@ -11,7 +11,7 @@
"preview": "yarn vite preview"
},
"dependencies": {
"framer-motion": "^12.24.2",
"framer-motion": "^12.24.7",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
Loading
Loading