Skip to content
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ Motion adheres to [Semantic Versioning](http://semver.org/).

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

## [12.24.11] 2026-01-08

### Fixed

- Fixed time sampling of GPU animations under heavy CPU load.

## [12.24.10] 2026-01-07

### Fixed

- Fixing missing import from `motion-dom`.

## [12.24.9] 2026-01-07

### Fixed
Expand Down
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,7 @@ async function nextFrame() {
- Prefer arrow callbacks
- Use strict equality (`===`)
- No `var` declarations (use `const`/`let`)

## Timing

Use `time.now()` from `motion-dom/src/frameloop/sync-time.ts` instead of `performance.now()` for frame-synced timestamps. This ensures consistent time measurements within synchronous contexts and proper sync with the animation frame loop.
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.10",
"version": "12.24.11",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -10,9 +10,9 @@
"preview": "vite preview"
},
"dependencies": {
"framer-motion": "^12.24.10",
"motion": "^12.24.10",
"motion-dom": "^12.24.10",
"framer-motion": "^12.24.11",
"motion": "^12.24.11",
"motion-dom": "^12.24.11",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
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.10",
"version": "12.24.11",
"type": "module",
"scripts": {
"dev": "next dev",
Expand All @@ -10,7 +10,7 @@
"build": "next build"
},
"dependencies": {
"motion": "^12.24.10",
"motion": "^12.24.11",
"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.10",
"version": "12.24.11",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -11,7 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"motion": "^12.24.10",
"motion": "^12.24.11",
"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.10",
"version": "12.24.11",
"type": "module",
"scripts": {
"dev": "yarn vite",
Expand All @@ -11,7 +11,7 @@
"preview": "yarn vite preview"
},
"dependencies": {
"framer-motion": "^12.24.10",
"framer-motion": "^12.24.11",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "12.24.10",
"version": "12.24.11",
"packages": [
"packages/*",
"dev/*"
Expand Down
4 changes: 2 additions & 2 deletions packages/framer-motion/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "framer-motion",
"version": "12.24.10",
"version": "12.24.11",
"description": "A simple and powerful JavaScript animation library",
"main": "dist/cjs/index.js",
"module": "dist/es/index.mjs",
Expand Down Expand Up @@ -88,7 +88,7 @@
"measure": "rollup -c ./rollup.size.config.mjs"
},
"dependencies": {
"motion-dom": "^12.24.10",
"motion-dom": "^12.24.11",
"motion-utils": "^12.24.10",
"tslib": "^2.4.0"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/motion-dom/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "motion-dom",
"version": "12.24.10",
"version": "12.24.11",
"author": "Matt Perry",
"license": "MIT",
"repository": "https://github.com/motiondivision/motion",
Expand Down
13 changes: 11 additions & 2 deletions packages/motion-dom/src/animation/NativeAnimation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ export class NativeAnimation<T extends AnyResolvedKeyframe>

private isPseudoElement: boolean

/**
* Tracks a manually-set start time that takes precedence over WAAPI's
* dynamic startTime. This is cleared when play() or time setter is called,
* allowing WAAPI to take over timing.
*/
protected manualStartTime: number | null = null

constructor(options?: NativeAnimationOptions) {
super()

Expand Down Expand Up @@ -118,6 +125,7 @@ export class NativeAnimation<T extends AnyResolvedKeyframe>
play() {
if (this.isStopped) return

this.manualStartTime = null
this.animation.play()

if (this.state === "finished") {
Expand Down Expand Up @@ -192,6 +200,7 @@ export class NativeAnimation<T extends AnyResolvedKeyframe>
}

set time(newTime: number) {
this.manualStartTime = null
this.finishedTime = null
this.animation.currentTime = secondsToMilliseconds(newTime)
}
Expand All @@ -218,11 +227,11 @@ export class NativeAnimation<T extends AnyResolvedKeyframe>
}

get startTime() {
return Number(this.animation.startTime)
return this.manualStartTime ?? Number(this.animation.startTime)
}

set startTime(newStartTime: number) {
this.animation.startTime = newStartTime
this.manualStartTime = this.animation.startTime = newStartTime
}

/**
Expand Down
17 changes: 12 additions & 5 deletions packages/motion-dom/src/animation/NativeAnimationExtended.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { secondsToMilliseconds } from "motion-utils"
import { clamp } from "motion-utils"
import { time } from "../frameloop/sync-time"
import { JSAnimation } from "./JSAnimation"
import { NativeAnimation, NativeAnimationOptions } from "./NativeAnimation"
import { AnyResolvedKeyframe, ValueAnimationOptions } from "./types"
Expand Down Expand Up @@ -43,7 +44,7 @@ export class NativeAnimationExtended<

super(options)

if (options.startTime) {
if (options.startTime !== undefined) {
this.startTime = options.startTime
}

Expand Down Expand Up @@ -74,12 +75,18 @@ export class NativeAnimationExtended<
autoplay: false,
})

const sampleTime = secondsToMilliseconds(this.finishedTime ?? this.time)
/**
* Use wall-clock elapsed time for sampling.
* Under CPU load, WAAPI's currentTime may not reflect actual
* elapsed time, causing incorrect sampling and visual jumps.
*/
const sampleTime = Math.max(sampleDelta, time.now() - this.startTime)
const delta = clamp(0, sampleDelta, sampleTime - sampleDelta)

motionValue.setWithVelocity(
sampleAnimation.sample(sampleTime - sampleDelta).value,
sampleAnimation.sample(Math.max(0, sampleTime - delta)).value,
sampleAnimation.sample(sampleTime).value,
sampleDelta
delta
)

sampleAnimation.stop()
Expand Down
4 changes: 2 additions & 2 deletions packages/motion/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "motion",
"version": "12.24.10",
"version": "12.24.11",
"description": "An animation library for JavaScript and React.",
"main": "dist/cjs/index.js",
"module": "dist/es/index.mjs",
Expand Down Expand Up @@ -76,7 +76,7 @@
"postpublish": "git push --tags"
},
"dependencies": {
"framer-motion": "^12.24.10",
"framer-motion": "^12.24.11",
"tslib": "^2.4.0"
},
"peerDependencies": {
Expand Down
1 change: 1 addition & 0 deletions tests/animate/animate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,3 +511,4 @@ test.describe("NativeAnimation", () => {
expect(await box.innerText()).toBe("finished")
})
})

22 changes: 11 additions & 11 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7420,14 +7420,14 @@ __metadata:
languageName: node
linkType: hard

"framer-motion@^12.24.10, framer-motion@workspace:packages/framer-motion":
"framer-motion@^12.24.11, framer-motion@workspace:packages/framer-motion":
version: 0.0.0-use.local
resolution: "framer-motion@workspace:packages/framer-motion"
dependencies:
"@radix-ui/react-dialog": ^1.1.15
"@thednp/dommatrix": ^2.0.11
"@types/three": 0.137.0
motion-dom: ^12.24.10
motion-dom: ^12.24.11
motion-utils: ^12.24.10
three: 0.137.0
tslib: ^2.4.0
Expand Down Expand Up @@ -8192,9 +8192,9 @@ __metadata:
version: 0.0.0-use.local
resolution: "html-env@workspace:dev/html"
dependencies:
framer-motion: ^12.24.10
motion: ^12.24.10
motion-dom: ^12.24.10
framer-motion: ^12.24.11
motion: ^12.24.11
motion-dom: ^12.24.11
react: ^18.3.1
react-dom: ^18.3.1
vite: ^5.2.0
Expand Down Expand Up @@ -10936,7 +10936,7 @@ __metadata:
languageName: node
linkType: hard

"motion-dom@^12.24.10, motion-dom@workspace:packages/motion-dom":
"motion-dom@^12.24.11, motion-dom@workspace:packages/motion-dom":
version: 0.0.0-use.local
resolution: "motion-dom@workspace:packages/motion-dom"
dependencies:
Expand Down Expand Up @@ -11013,11 +11013,11 @@ __metadata:
languageName: unknown
linkType: soft

"motion@^12.24.10, motion@workspace:packages/motion":
"motion@^12.24.11, motion@workspace:packages/motion":
version: 0.0.0-use.local
resolution: "motion@workspace:packages/motion"
dependencies:
framer-motion: ^12.24.10
framer-motion: ^12.24.11
tslib: ^2.4.0
peerDependencies:
"@emotion/is-prop-valid": "*"
Expand Down Expand Up @@ -11134,7 +11134,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "next-env@workspace:dev/next"
dependencies:
motion: ^12.24.10
motion: ^12.24.11
next: 15.4.10
react: 19.0.0
react-dom: 19.0.0
Expand Down Expand Up @@ -12599,7 +12599,7 @@ __metadata:
"@typescript-eslint/parser": ^7.2.0
"@vitejs/plugin-react-swc": ^3.5.0
eslint-plugin-react-refresh: ^0.4.6
motion: ^12.24.10
motion: ^12.24.11
react: ^19.0.0
react-dom: ^19.0.0
vite: ^5.2.0
Expand Down Expand Up @@ -12683,7 +12683,7 @@ __metadata:
"@typescript-eslint/parser": ^7.2.0
"@vitejs/plugin-react-swc": ^3.5.0
eslint-plugin-react-refresh: ^0.4.6
framer-motion: ^12.24.10
framer-motion: ^12.24.11
react: ^18.3.1
react-dom: ^18.3.1
vite: ^5.2.0
Expand Down
Loading