Skip to content

[BUG] animate() writes CSS variables on SVG elements as attributes since 13.3.0 #3825

Description

@joelfillmore

Describe the bug

Since framer-motion 13.3.0 (motion 13.3.0 and 13.4.0), animate() on an SVG element writes CSS custom properties as attributes (<path --x="0.68">) instead of setting them with style.setProperty. CSS var() never reads attributes, so anything driven by the variable stops animating. HTML elements are unaffected. Both the single-element and the sequence forms of animate() show it.

Real-world case: we trim an SVG path with stroke-dasharray: calc(var(--trim-end) - var(--trim-start)) 2 and animate the two variables with animate(). On 13.3.0 the trim no longer moves.

IMPORTANT: Provide a CodeSandbox reproduction of the bug

https://codesandbox.io/p/sandbox/d8crww

It runs the same animate() call against 13.2.0 and 13.4.0 side by side (via esm.sh with pinned deps). The 13.2.0 path trim animates; the 13.4.0 one stays a full line, and the readout shows --x landing in an attribute instead of style.

Steps to reproduce

import { animate, frame } from "motion"

document.body.innerHTML = `
  <svg width="100" height="100"><path id="p" d="M0 0L100 100" stroke="black"/></svg>
  <div id="d"></div>`
const path = document.getElementById("p")
const div = document.getElementById("d")

for (const el of [path, div]) {
  const a = animate(el, { "--x": [0, 1] }, { duration: 1, autoplay: false })
  a.time = 0.5
}

const tick = () => new Promise((r) => frame.postRender(r))
await tick()
await tick()

console.log(path.outerHTML)
console.log(JSON.stringify({
  svg: path.style.getPropertyValue("--x"),
  html: div.style.getPropertyValue("--x"),
}))

The sequence form animate([[path, { "--y": [0, 1] }, { duration: 1 }]]) gives the same result.

Expected behavior

--x lands in inline style on the SVG path, as it does on the div. This is what 13.2.0 prints:

<path id="p" d="M0 0L100 100" stroke="black" style="--x: 0.6846233151445631;"></path>
{"svg":"0.6846233151445631","html":"0.6846233151445631"}

Actual output on 13.3.0 and 13.4.0:

<path id="p" d="M0 0L100 100" stroke="black" --x="0.6846233151445631"></path>
{"svg":"","html":"0.6846233151445631"}

Note for reproducing the working case: npm i motion@13.2.0 alone still installs framer-motion 13.4.0 via ^13.2.0. Add an override pinning framer-motion to 13.2.0. framer-motion 13.2.0 with motion-dom 13.3.0 works, so the regression comes in with framer-motion 13.3.0.

Environment details

Chromium via Playwright 1.63, macOS. motion 13.2.0 / 13.3.0 / 13.4.0.

Cause

Suggested fix

Route CSS variables to the style writer in addSVGValue, e.g. isCSSVar(key) || key in element.style ? addStyleValue : addAttrValue, and add an SVG case next to the existing CSS-variable test.

Let me know if you'd like me to submit a PR with this approach.

Related change, possibly intended

Animating fill on an SVG path now writes inline style and leaves the fill attribute alone, where 13.2.0 animated the attribute. This changes rendering when author CSS also sets fill: with a stylesheet rule fill: green, 13.2.0's animation lost to the stylesheet, and 13.3.0's inline style wins. Mentioning it in case it wasn't deliberate (see also #3779).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions