Skip to content

Commit

Permalink
Don't rerun keyframes animation if all keyframes match (#2466)
Browse files Browse the repository at this point in the history
* Don't rerun animation if keyframes haven't changed

* Revert test
  • Loading branch information
mattgperry authored Jan 3, 2024
1 parent 4274c7b commit a6b5527
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ describe("keyframes transition", () => {
expect(xResult).toBe(100)
})

test("keyframes animation reruns when variants change and keyframes are the same", async () => {
test("keyframes animation doesn't rerun when variants change and keyframes are the same", async () => {
const xResult = await new Promise((resolve) => {
const x = motionValue(0)
const Component = ({ animate }: any) => {
Expand All @@ -112,7 +112,7 @@ describe("keyframes transition", () => {
setTimeout(() => resolve(x.get()), 50)
})

expect(xResult).toBe(50)
expect(xResult).toBe(100)
})

test("times works as expected", async () => {
Expand Down
51 changes: 51 additions & 0 deletions packages/framer-motion/src/motion/__tests__/variant.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,57 @@ describe("animate prop as variant", () => {
expect(element).toHaveStyle("opacity: 0")
})

test("variants work the same whether defined inline or not", async () => {
const variants = {
foo: { opacity: [1, 0, 1] },
}
const outputA: number[] = []
const outputB: number[] = []
const Component = ({
activeVariants,
}: {
activeVariants: string[]
}) => {
return (
<>
<motion.div
className="box bg-blue"
animate={activeVariants}
variants={{
foo: {
opacity: [1, 0, 1],
},
}}
transition={{ duration: 0.1 }}
onUpdate={({ opacity }) =>
outputA.push(opacity as number)
}
/>
<motion.div
className="box bg-green"
animate={activeVariants}
variants={variants}
transition={{ duration: 0.1 }}
onUpdate={({ opacity }) =>
outputB.push(opacity as number)
}
/>
</>
)
}

const { rerender } = render(<Component activeVariants={["foo"]} />)
rerender(<Component activeVariants={["foo"]} />)
await new Promise((resolve) => {
setTimeout(() => {
rerender(<Component activeVariants={["foo", "bar"]} />)
setTimeout(resolve, 100)
}, 100)
})

expect(outputA).toEqual(outputB)
})

test("style is used as fallback when a variant changes to not contain that style", async () => {
const Component = ({ animate }: { animate?: string }) => {
return (
Expand Down
25 changes: 9 additions & 16 deletions packages/framer-motion/src/render/utils/animation-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,22 +253,15 @@ export function createAnimationState(
/**
* If the value has changed, we probably want to animate it.
*/
if (next !== prev) {
/**
* If both values are keyframes, we need to shallow compare them to
* detect whether any value has changed. If it has, we animate it.
*/
if (isKeyframesTarget(next) && isKeyframesTarget(prev)) {
if (!shallowCompare(next, prev) || variantDidChange) {
markToAnimate(key)
} else {
/**
* If it hasn't changed, we want to ensure it doesn't animate by
* adding it to the list of protected keys.
*/
typeState.protectedKeys[key] = true
}
} else if (next !== undefined) {
let valueHasChanged = false
if (isKeyframesTarget(next) && isKeyframesTarget(prev)) {
valueHasChanged = !shallowCompare(next, prev)
} else {
valueHasChanged = next !== prev
}

if (valueHasChanged) {
if (next !== undefined) {
// If next is defined and doesn't equal prev, it needs animating
markToAnimate(key)
} else {
Expand Down

0 comments on commit a6b5527

Please sign in to comment.