diff --git a/packages/framer-motion/package.json b/packages/framer-motion/package.json
index c91217e49a..7414141e3d 100644
--- a/packages/framer-motion/package.json
+++ b/packages/framer-motion/package.json
@@ -98,15 +98,15 @@
"bundlesize": [
{
"path": "./dist/size-rollup-motion.js",
- "maxSize": "34.05 kB"
+ "maxSize": "33.95 kB"
},
{
"path": "./dist/size-rollup-m.js",
- "maxSize": "6 kB"
+ "maxSize": "5.9 kB"
},
{
"path": "./dist/size-rollup-dom-animation.js",
- "maxSize": "17 kB"
+ "maxSize": "16.9 kB"
},
{
"path": "./dist/size-rollup-dom-max.js",
@@ -114,7 +114,7 @@
},
{
"path": "./dist/size-rollup-animate.js",
- "maxSize": "18 kB"
+ "maxSize": "17.9 kB"
}
],
"gitHead": "dcf88c8f6c178d1af7a8f7dec81326db5fd68cea"
diff --git a/packages/framer-motion/src/motion/__tests__/variant.test.tsx b/packages/framer-motion/src/motion/__tests__/variant.test.tsx
index 5fae8c4b14..1f048eaddb 100644
--- a/packages/framer-motion/src/motion/__tests__/variant.test.tsx
+++ b/packages/framer-motion/src/motion/__tests__/variant.test.tsx
@@ -1295,4 +1295,32 @@ describe("animate prop as variant", () => {
return expect(promise).resolves.toBe("visible")
})
+
+ test("changing values within an inherited variant triggers an animation", async () => {
+ const Component = ({ x }: { x: number }) => {
+ return (
+
+
+
+ )
+ }
+
+ const { rerender, getByTestId } = render()
+
+ await nextFrame()
+
+ const element = getByTestId("element")
+
+ expect(element).toHaveStyle("transform: none")
+
+ rerender()
+
+ await nextFrame()
+
+ expect(element).toHaveStyle("transform: translateX(100px)")
+ })
})
diff --git a/packages/framer-motion/src/render/VisualElement.ts b/packages/framer-motion/src/render/VisualElement.ts
index a1ae9e9a91..23e0953d64 100644
--- a/packages/framer-motion/src/render/VisualElement.ts
+++ b/packages/framer-motion/src/render/VisualElement.ts
@@ -18,7 +18,6 @@ import { isMotionValue } from "../value/utils/is-motion-value"
import { transformProps } from "./html/utils/transform"
import {
ResolvedValues,
- VariantStateContext,
VisualElementEventCallbacks,
VisualElementOptions,
} from "./types"
@@ -27,14 +26,12 @@ import {
isControllingVariants as checkIsControllingVariants,
isVariantNode as checkIsVariantNode,
} from "./utils/is-controlling-variants"
-import { isVariantLabel } from "./utils/is-variant-label"
import { updateMotionValuesFromProps } from "./utils/motion-values"
import { resolveVariantFromProps } from "./utils/resolve-variants"
import { warnOnce } from "../utils/warn-once"
import { featureDefinitions } from "../motion/features/definitions"
import { Feature } from "../motion/features/Feature"
import type { PresenceContextProps } from "../context/PresenceContext"
-import { variantProps } from "./utils/variant-props"
import { visualElementStore } from "./store"
import { KeyframeResolver } from "./utils/KeyframesResolver"
import { isNumericalString } from "../utils/is-numerical-string"
@@ -54,8 +51,6 @@ const propEventHandlers = [
"LayoutAnimationComplete",
] as const
-const numVariantProps = variantProps.length
-
/**
* A VisualElement is an imperative abstraction around UI elements such as
* HTMLElement, SVGElement, Three.Object3D etc.
@@ -655,34 +650,6 @@ export abstract class VisualElement<
: undefined
}
- getVariantContext(startAtParent = false): undefined | VariantStateContext {
- if (startAtParent) {
- return this.parent ? this.parent.getVariantContext() : undefined
- }
-
- if (!this.isControllingVariants) {
- const context = this.parent
- ? this.parent.getVariantContext() || {}
- : {}
- if (this.props.initial !== undefined) {
- context.initial = this.props.initial as any
- }
- return context
- }
-
- const context = {}
- for (let i = 0; i < numVariantProps; i++) {
- const name = variantProps[i] as keyof typeof context
- const prop = this.props[name]
-
- if (isVariantLabel(prop) || prop === false) {
- context[name] = prop
- }
- }
-
- return context
- }
-
/**
* Add a child visual element to our set of children.
*/
diff --git a/packages/framer-motion/src/render/types.ts b/packages/framer-motion/src/render/types.ts
index 730ba192a1..340344c200 100644
--- a/packages/framer-motion/src/render/types.ts
+++ b/packages/framer-motion/src/render/types.ts
@@ -16,16 +16,6 @@ export interface MotionPoint {
y: MotionValue
}
-export type VariantStateContext = {
- initial?: string | string[]
- animate?: string | string[]
- exit?: string | string[]
- whileHover?: string | string[]
- whileDrag?: string | string[]
- whileFocus?: string | string[]
- whileTap?: string | string[]
-}
-
export type ScrapeMotionValuesFromProps = (
props: MotionProps,
prevProps: MotionProps,
diff --git a/packages/framer-motion/src/render/utils/animation-state.ts b/packages/framer-motion/src/render/utils/animation-state.ts
index ee8d631007..bd765e4873 100644
--- a/packages/framer-motion/src/render/utils/animation-state.ts
+++ b/packages/framer-motion/src/render/utils/animation-state.ts
@@ -12,6 +12,7 @@ import { VisualElementAnimationOptions } from "../../animation/interfaces/types"
import { AnimationDefinition } from "../../animation/types"
import { animateVisualElement } from "../../animation/interfaces/visual-element"
import { ResolvedValues } from "../types"
+import { getVariantContext } from "./get-variant-context"
export interface AnimationState {
animateChanges: (type?: AnimationType) => Promise
@@ -96,8 +97,8 @@ export function createAnimationState(
* what to animate those to.
*/
function animateChanges(changedActiveType?: AnimationType) {
- const props = visualElement.getProps()
- const context = visualElement.getVariantContext(true) || {}
+ const { props } = visualElement
+ const context = getVariantContext(visualElement.parent) || {}
/**
* A list of animations that we'll build into as we iterate through the animation
@@ -314,9 +315,12 @@ export function createAnimationState(
}
/**
- * If this is an inherited prop we want to hard-block animations
+ * If this is an inherited prop we want to skip this animation
+ * unless the inherited variants haven't changed on this render.
*/
- if (shouldAnimateType && (!isInherited || handledRemovedValues)) {
+ const willAnimateViaParent = isInherited && variantDidChange
+ const needsAnimating = !willAnimateViaParent || handledRemovedValues
+ if (shouldAnimateType && needsAnimating) {
animations.push(
...definitionList.map((animation) => ({
animation: animation as AnimationDefinition,
diff --git a/packages/framer-motion/src/render/utils/get-variant-context.ts b/packages/framer-motion/src/render/utils/get-variant-context.ts
new file mode 100644
index 0000000000..f2c9089f50
--- /dev/null
+++ b/packages/framer-motion/src/render/utils/get-variant-context.ts
@@ -0,0 +1,43 @@
+import { VisualElement } from "../VisualElement"
+import { isVariantLabel } from "./is-variant-label"
+import { variantProps } from "./variant-props"
+
+const numVariantProps = variantProps.length
+
+type VariantStateContext = {
+ initial?: string | string[]
+ animate?: string | string[]
+ exit?: string | string[]
+ whileHover?: string | string[]
+ whileDrag?: string | string[]
+ whileFocus?: string | string[]
+ whileTap?: string | string[]
+}
+
+export function getVariantContext(
+ visualElement?: VisualElement
+): undefined | VariantStateContext {
+ if (!visualElement) return undefined
+
+ if (!visualElement.isControllingVariants) {
+ const context = visualElement.parent
+ ? getVariantContext(visualElement.parent) || {}
+ : {}
+ if (visualElement.props.initial !== undefined) {
+ context.initial = visualElement.props.initial as any
+ }
+ return context
+ }
+
+ const context = {}
+ for (let i = 0; i < numVariantProps; i++) {
+ const name = variantProps[i] as keyof typeof context
+ const prop = visualElement.props[name]
+
+ if (isVariantLabel(prop) || prop === false) {
+ context[name] = prop
+ }
+ }
+
+ return context
+}