motiondivision / motiondivision/motion

[BUG] Animations start late with Chrome DevTools playback at 10%

Open
#3,820 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
33.7k
Forks
1.4k
Avg merge
1d 10h
Merged PRs (30d)
14

Description

Motion 13.2.0 opacity animations can wait tens of seconds before starting when Chrome DevTools Animations playback is set to 10%. Once they start, they animate at the correct slowed speed. A native Web Animations control starts immediately and respects the same slowdown.

The failure reproduces with a permanently mounted element, without AnimatePresence, conditional rendering, keys, or entry/exit animations.

Reproduction

CodeSandbox source · Standalone preview

Open the standalone preview in its own tab for the DevTools playback test. It contains the Motion fade and a native Web Animations control driven by the same button.

React 18.3.1, motion 13.2.0, motion-dom 13.2.0. Automated inspection used Chrome reporting version 153.0.0.0 on macOS. Motion 13.2.0 was the npm latest release when checked on 13 September 2026. Other browser versions and Motion versions have not been tested.

import { useState } from "react";
import { motion } from "motion/react";

export default function App() {
  const [visible, setVisible] = useState(true);
  return (
    <>
      <button onClick={() => setVisible(!visible)}>Toggle</button>
      <motion.div
        initial={false}
        animate={{ opacity: visible ? 1 : 0 }}
        transition={{ duration: 0.2, ease: "linear" }}
      >
        Always mounted
      </motion.div>
    </>
  );
}
  1. Verify that toggling at 100% works.
  2. Open DevTools, More tools, Animations, and set playback to 10%.
  3. Wait approximately 10 seconds, then toggle.
  4. The element waits before starting its approximately two-second fade. The wait depends on accumulated clock divergence.

Expected: the fade begins immediately and takes approximately two seconds.

Actual: the fade begins after a long delay. A manual observation was approximately 84 seconds. A separate instrumented run measured a negative animation current time that predicted approximately 128 seconds of remaining delay; that run was corrected before waiting for natural completion.

Root-cause evidence

In the installed motion-dom 13.2.0 implementation:

  • frameloop/sync-time.mjs obtains time from the frame timestamp or performance.now().
  • animation/AsyncMotionValueAnimation.mjs chooses a creation/resolution timestamp as startTime.
  • animation/NativeAnimationExtended.mjs forwards the supplied start time.
  • The setter in animation/NativeAnimation.mjs assigns it directly to the underlying WAAPI animation.

DevTools slows the document timeline while the JavaScript performance clock continues advancing at normal speed. The animation start time is therefore ahead of its timeline. This matches the Web Animations timing model, which calculates animation time from timeline time minus start time, multiplied by animation playback rate.

Approximately 100 ms after one toggle, the measurements were:

Measurement (ms) Motion Native control
Document timeline 105163.230 105163.230
Animation start time 117965.700 105153.190
Animation current time -12802.470 10.040
Computed opacity, fading from 0 to 1 0 0.0502

The JavaScript performance clock read 118066 ms. Both animations reported individual playbackRate 1; DevTools slowed their shared document timeline.

At 10% timeline speed, a deficit of 12802.470 timeline milliseconds takes approximately 128 seconds of real time to recover.

Two interventions isolate the cause:

  1. Changing only the delayed Motion animation's startTime to document.timeline.currentTime made it advance immediately. After approximately 100 ms of real time, currentTime was 10.024 ms and opacity was 0.05012. It had completed at the sample approximately 2.3 seconds later.
  2. Without Motion, assigning performance.now() to a native animation's startTime reproduced the delay: currentTime was -32528.570 ms. An otherwise equivalent native animation assigned document.timeline.currentTime advanced to 11.704 ms over the same interval.

The comparison can be performed on two plain elements while DevTools is slowed:

const options = { duration: 200, easing: "linear", fill: "both" };
const frames = [{ opacity: 0 }, { opacity: 1 }];
const a = firstElement.animate(frames, options);
a.startTime = performance.now();
const b = secondElement.animate(frames, options);
b.startTime = document.timeline.currentTime;

Possible direction

The evidence points to using a timestamp outside the WAAPI timeline's clock domain when scheduling the native animation. Leaving native start-time scheduling to the browser or expressing the start time in the animation timeline's clock domain avoids the observed delay.

The runtime intervention is diagnostic, not a proposed production patch. A library fix needs to preserve Motion's synchronization, interruption, handoff, and custom-timeline behavior. These tests establish the mechanism in the tested configuration; they do not establish browser compatibility or whether DevTools should offer additional compatibility handling.

Measurement method

Chrome DevTools MCP evaluated scripts in the reproduction page. A wrapper around the native Animation.prototype.startTime setter recorded assigned values and both clock readings, then forwarded each value unchanged to the original setter. Samples read document.getAnimations(), animation timestamps, and computed opacity. The delay was also observed manually before instrumentation.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the CodeSandbox reproduction, then read frameloop/sync-time.mjs and the start-time handling in animation/AsyncMotionValueAnimation.mjs, animation/NativeAnimationExtended.mjs, and animation/NativeAnimation.mjs. Reproduce the delay with Chrome DevTools playback at 10% and compare Motion with the native control. Done means the fade starts immediately at slowed playback while preserving synchronization, interruption, handoff, and custom-timeline behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend, performance, web-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.