Skip to content

Commit

Permalink
Animate values when child variant definitions change (#2804)
Browse files Browse the repository at this point in the history
* Animate values when child variant definitions change

* Fixing test

* Updating bundlesize
  • Loading branch information
mattgperry committed Sep 19, 2024
1 parent f7b6212 commit 111c7c0
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 51 deletions.
8 changes: 4 additions & 4 deletions packages/framer-motion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,23 +98,23 @@
"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",
"maxSize": "29 kB"
},
{
"path": "./dist/size-rollup-animate.js",
"maxSize": "18 kB"
"maxSize": "17.9 kB"
}
],
"gitHead": "dcf88c8f6c178d1af7a8f7dec81326db5fd68cea"
Expand Down
28 changes: 28 additions & 0 deletions packages/framer-motion/src/motion/__tests__/variant.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<motion.div initial={false} animate="variant">
<motion.div
data-testid="element"
variants={{ variant: { x } }}
transition={{ type: false }}
/>
</motion.div>
)
}

const { rerender, getByTestId } = render(<Component x={0} />)

await nextFrame()

const element = getByTestId("element")

expect(element).toHaveStyle("transform: none")

rerender(<Component x={100} />)

await nextFrame()

expect(element).toHaveStyle("transform: translateX(100px)")
})
})
33 changes: 0 additions & 33 deletions packages/framer-motion/src/render/VisualElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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.
*/
Expand Down
10 changes: 0 additions & 10 deletions packages/framer-motion/src/render/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,6 @@ export interface MotionPoint {
y: MotionValue<number>
}

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,
Expand Down
12 changes: 8 additions & 4 deletions packages/framer-motion/src/render/utils/animation-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
43 changes: 43 additions & 0 deletions packages/framer-motion/src/render/utils/get-variant-context.ts
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 111c7c0

Please sign in to comment.