Skip to content

Skip WAAPI for non-animatable keyframe values#3631

Merged
mattgperry merged 1 commit intomainfrom
worktree-fix-issue-3102
Mar 12, 2026
Merged

Skip WAAPI for non-animatable keyframe values#3631
mattgperry merged 1 commit intomainfrom
worktree-fix-issue-3102

Conversation

@mattgperry
Copy link
Collaborator

Summary

  • When canAnimate() determines keyframe values aren't animatable (e.g. bare filter function names like "blur" without parentheses), the animation is made instant but was still routed through WAAPI
  • WAAPI's element.animate() then received the invalid CSS values and produced Chrome's "Invalid keyframe value for property filter: blur" console warning
  • Now we skip WAAPI when canAnimate() returns false, falling through to JSAnimation which handles instant animations without triggering browser warnings

Bug

When animating filter with values like blur(10px), Chrome could produce "Invalid keyframe value for property filter: blur" warnings — particularly during HMR re-renders or when non-animatable filter values reached the WAAPI path.

Cause

AsyncMotionValueAnimation.onKeyframesResolved() correctly detected non-animatable values via canAnimate() and made the animation instant, but supportsBrowserAnimation() still returned true for the filter property (since it's in the accelerated values set). This caused the potentially invalid keyframe values to be passed to element.animate().

Fix

Added a canAnimateValue flag that prevents WAAPI from being used when canAnimate() returns false. Non-animatable instant animations now always use JSAnimation, which doesn't produce browser warnings for invalid CSS values.

Fixes #3102

🤖 Generated with Claude Code

…ings

When canAnimate() returns false (e.g. for bare filter values like "blur"
without parentheses), the resolved keyframes may not be valid CSS. Previously
these invalid values were still passed to element.animate() via WAAPI,
triggering Chrome's "Invalid keyframe value" console warning. Now we skip
WAAPI in this case and fall through to JSAnimation which handles the instant
animation without browser warnings.

Fixes #3102

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@greptile-apps
Copy link

greptile-apps bot commented Mar 11, 2026

Greptile Summary

This PR fixes a Chrome console warning ("Invalid keyframe value for property filter: blur") that appeared when animating filter properties with non-animatable values (e.g. bare function names like "blur" without parentheses) — particularly visible during HMR re-renders.

Root cause: AsyncMotionValueAnimation.onKeyframesResolved() correctly identified non-animatable keyframes via canAnimate() and made the animation instant, but useWaapi was computed independently and still routed the (potentially invalid) CSS values to element.animate().

Fix: A canAnimateValue boolean flag is set to false when canAnimate() returns falsy, and that flag is ORed into the useWaapi guard. Non-animatable animations now always fall through to JSAnimation, which handles instant playback without issuing browser warnings.

Key changes:

  • AsyncMotionValueAnimation.ts: single-flag gate (canAnimateValue) added to the useWaapi decision — minimal and correct
  • New unit tests for canAnimate and isAnimatable utilities covering the filter blur edge cases
  • New React dev fixture + Cypress integration test for the HMR re-animation scenario
  • One Cypress test case ("uses WAAPI for valid filter blur animations") contains a vacuously-true assertion that does not actually verify WAAPI usage — worth cleaning up to avoid misleading future readers

Confidence Score: 5/5

  • This PR is safe to merge — the change is a narrow, well-scoped guard with no risk of regression for valid animation paths.
  • The core change is a single boolean flag (canAnimateValue) that only affects the WAAPI branch when canAnimate() has already determined the animation should be instant. Valid keyframes continue through the unchanged WAAPI path. The fallback JSAnimation already handles instant animations correctly. New unit tests and an integration test cover the reported scenario.
  • The Cypress test file animate-filter-blur.ts has a misleading second test case with a vacuously-true assertion — not a blocker but worth addressing.

Important Files Changed

Filename Overview
packages/motion-dom/src/animation/AsyncMotionValueAnimation.ts Core fix: introduces canAnimateValue flag to short-circuit useWaapi when canAnimate() returns false, preventing invalid CSS values from reaching element.animate(). Logic is minimal, correct, and well-scoped.
packages/framer-motion/cypress/integration/animate-filter-blur.ts Cypress integration test added for issue #3102. First test case is solid; second test ("uses WAAPI…") contains a trivially-true expect(el).to.exist assertion that does not verify the stated claim.
dev/react/src/tests/animate-filter-blur.tsx New React test fixture for the filter-blur HMR scenario. Straightforward component that drives two sequential animations and exposes state via DOM text nodes for Cypress to assert on.
packages/motion-dom/src/animation/utils/tests/can-animate.test.ts New unit tests for canAnimate. Covers valid blur keyframes, bare function names, all-non-animatable keyframes, null origin, and opacity — good baseline coverage for the affected code path.
packages/motion-dom/src/animation/utils/tests/is-animatable.test.ts New unit tests for isAnimatable. Tests valid/invalid filter values, complex filter chains, bare numeric values, and non-animatable strings like none and url(…).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[onKeyframesResolved] --> B{canAnimate keyframes?}
    B -- false --> C[canAnimateValue = false]
    C --> D{instantAnimations OR no delay?}
    D -- yes --> E[onUpdate with final keyframe]
    D -- no --> F[skip immediate update]
    E --> G[makeAnimationInstant\nrepeat = 0]
    F --> G
    B -- true --> H[canAnimateValue = true]
    H --> I{useWaapi?\ncanAnimateValue AND\nnot isHandoff AND\nsupportsBrowserAnimation}
    G --> I
    I -- true --> J[NativeAnimationExtended\nWAAPI path]
    I -- false --> K[JSAnimation\nJS fallback path]
    J --> L[animation.finished → notifyFinished]
    K --> L
Loading

Last reviewed commit: b3acc38

Comment on lines +21 to +33

it("uses WAAPI for valid filter blur animations", () => {
cy.visit("?test=animate-filter-blur")

// During animation, the element should have a WAAPI animation
cy.get("#box").should(($el) => {
const el = $el[0] as HTMLElement
// We can check that a WAAPI animation was created
// (it may have already finished by the time we check,
// so we just verify no error occurred)
expect(el).to.exist
})

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vacuous assertion doesn't verify the test title's claim

The second it block is titled "uses WAAPI for valid filter blur animations" but the only assertion made is expect(el).to.exist, which is always true for any element matched by a Cypress selector. The test provides zero coverage of the WAAPI claim, and its comment even acknowledges this ("We can check that a WAAPI animation was created... so we just verify no error occurred"). This is misleading to anyone reading the test suite, since it appears to verify WAAPI usage but does nothing of the sort.

Consider either removing this test (the first it already covers "animations complete without error") or replacing the assertion with something meaningful — e.g. querying el.getAnimations() during the animation phase to verify a WAAPI Animation object is present.

@mattgperry mattgperry merged commit eb889b4 into main Mar 12, 2026
8 checks passed
@mattgperry mattgperry deleted the worktree-fix-issue-3102 branch March 12, 2026 05:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Warning, Invalid Keyframe Value for Property

1 participant