From a2b81acd1749cef70af692fe451cb5b6d2b4c072 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Tue, 15 Oct 2024 10:23:46 +0200 Subject: [PATCH] Remove auto `will-change` (#2824) * Applying animating value names to will-change * Reverting will-change * Fixing tests --- .../gestures/drag/__tests__/index.test.tsx | 46 ++--- .../src/motion/utils/use-visual-state.ts | 17 +- .../__tests__/will-change.ssr.test.tsx | 116 ------------- .../__tests__/will-change.test.tsx | 161 +++++------------- .../value/use-will-change/add-will-change.ts | 10 +- 5 files changed, 86 insertions(+), 264 deletions(-) delete mode 100644 packages/framer-motion/src/value/use-will-change/__tests__/will-change.ssr.test.tsx diff --git a/packages/framer-motion/src/gestures/drag/__tests__/index.test.tsx b/packages/framer-motion/src/gestures/drag/__tests__/index.test.tsx index 39b998ca87..eff96051df 100644 --- a/packages/framer-motion/src/gestures/drag/__tests__/index.test.tsx +++ b/packages/framer-motion/src/gestures/drag/__tests__/index.test.tsx @@ -1,6 +1,12 @@ import { useState } from "react" import { pointerDown, render } from "../../../../jest.setup" -import { BoundingBox, motion, motionValue, MotionValue } from "../../../" +import { + BoundingBox, + motion, + motionValue, + MotionValue, + useWillChange, +} from "../../../" import { MockDrag, drag, deferred, dragFrame, Point, sleep } from "./utils" import { nextFrame } from "../../__tests__/utils" import { WillChangeMotionValue } from "../../../value/use-will-change/WillChangeMotionValue" @@ -57,21 +63,25 @@ describe("dragging", () => { }) test("willChange is applied correctly when other values are animating", async () => { - const Component = () => ( - - - - ) + const Component = () => { + const willChange = useWillChange() + return ( + + + + ) + } const { container, getByTestId, rerender } = render() rerender() @@ -322,10 +332,6 @@ describe("dragging", () => { const endValue = await new Promise((resolve) => { setTimeout(() => { - expect(container.firstChild).toHaveStyle( - "will-change: transform;" - ) - resolve(x.get()) }, 40) }) diff --git a/packages/framer-motion/src/motion/utils/use-visual-state.ts b/packages/framer-motion/src/motion/utils/use-visual-state.ts index 21fa9ccb42..13d8086615 100644 --- a/packages/framer-motion/src/motion/utils/use-visual-state.ts +++ b/packages/framer-motion/src/motion/utils/use-visual-state.ts @@ -106,7 +106,8 @@ function makeLatestValues( scrapeMotionValues: ScrapeMotionValuesFromProps ) { const values: ResolvedValues = {} - let applyWillChange = + const willChange = new Set() + const applyWillChange = shouldApplyWillChange && props.style?.willChange === undefined const motionValues = scrapeMotionValues(props, {}) @@ -171,15 +172,19 @@ function makeLatestValues( if (applyWillChange) { if (animate && initial !== false && !isAnimationControls(animate)) { forEachDefinition(props, animate, (target) => { - for (const key in target) { - const willChangeName = getWillChangeName(key) - if (willChangeName) { - values.willChange = "transform" - return + for (const name in target) { + const memberName = getWillChangeName(name) + + if (memberName) { + willChange.add(memberName) } } }) } + + if (willChange.size) { + values.willChange = Array.from(willChange).join(",") + } } return values diff --git a/packages/framer-motion/src/value/use-will-change/__tests__/will-change.ssr.test.tsx b/packages/framer-motion/src/value/use-will-change/__tests__/will-change.ssr.test.tsx deleted file mode 100644 index f692225bd8..0000000000 --- a/packages/framer-motion/src/value/use-will-change/__tests__/will-change.ssr.test.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { renderToString, renderToStaticMarkup } from "react-dom/server" -import { MotionConfig, motion } from "../../../" -import { motionValue } from "../../../value" - -function runTests(render: (components: any) => string) { - test("will-change correctly applied", () => { - const div = render( - - ) - - expect(div).toBe( - `
` - ) - }) - - test("will-change not set in static mode", () => { - const div = render( - - - - ) - - expect(div).toBe( - `
` - ) - }) - - test("will-change manually set", () => { - const div = render( - - ) - - expect(div).toBe( - `
` - ) - }) - - test("will-change manually set without animated values", () => { - const div = render() - - expect(div).toBe(`
`) - }) - - test("will-change not set without animated values", () => { - const div = render() - - expect(div).toBe(`
`) - }) - - test("Externally defined MotionValues not automatically added to will-change", () => { - const opacity = motionValue(0.5) - const div = render() - - expect(div).toBe(`
`) - }) - - test("will-change manually set by MotionValue", () => { - const willChange = motionValue("opacity") - const div = render( - - ) - - expect(div).toBe( - `
` - ) - }) - - test("will-change correctly not applied when isStatic", () => { - const div = render( - - - - ) - - expect(div).toBe( - `
` - ) - }) -} - -describe("render", () => { - runTests(renderToString) -}) - -describe("renderToStaticMarkup", () => { - runTests(renderToStaticMarkup) -}) diff --git a/packages/framer-motion/src/value/use-will-change/__tests__/will-change.test.tsx b/packages/framer-motion/src/value/use-will-change/__tests__/will-change.test.tsx index 9ca19cd1ac..a2066fcdd2 100644 --- a/packages/framer-motion/src/value/use-will-change/__tests__/will-change.test.tsx +++ b/packages/framer-motion/src/value/use-will-change/__tests__/will-change.test.tsx @@ -1,5 +1,5 @@ import { render } from "../../../../jest.setup" -import { MotionConfig, frame, motion, useMotionValue } from "../../.." +import { frame, motion, useMotionValue, useWillChange } from "../../.." import { nextFrame } from "../../../gestures/__tests__/utils" import { WillChangeMotionValue } from "../WillChangeMotionValue" @@ -17,62 +17,29 @@ describe("WillChangeMotionValue", () => { }) describe("willChange", () => { - test("Don't apply will-change if nothing has been defined", async () => { - const Component = () => - const { container } = render() - expect(container.firstChild).not.toHaveStyle("will-change: auto;") - }) - - test("If will-change is set via style, render that value", async () => { - const Component = () => { - return - } - const { container } = render() - expect(container.firstChild).toHaveStyle("will-change: transform;") - }) - test("Renders values defined in animate on initial render", async () => { const Component = () => { const opacity = useMotionValue(0) + const willChange = useWillChange() return ( ) } - const { container } = render() - - expect(container.firstChild).toHaveStyle("will-change: transform;") - }) - - test("Static mode: Doesn't render values defined in animate on initial render", async () => { - const Component = () => { - return ( - - - - ) - } - - const { container } = render() - - expect(container.firstChild).not.toHaveStyle("will-change: transform;") - }) + const { container, rerender } = render() + rerender() - test("Renders values defined in animate on initial render", async () => { - const Component = () => { - return - } - - const { container } = render() + await nextFrame() expect(container.firstChild).toHaveStyle("will-change: transform;") }) test("Doesn't render CSS variables or non-hardware accelerated values", async () => { const Component = () => { + const willChange = useWillChange() return ( { "--test": "#000", } as any } + style={{ willChange }} /> ) } const { container } = render() - expect(container.firstChild).toHaveStyle("will-change: transform;") - }) - - test("Don't render values defined in animate on initial render if initial is false", async () => { - const Component = () => { - return ( - - ) - } - - const { container } = render() + await nextFrame() - expect(container.firstChild).not.toHaveStyle("will-change: auto;") - expect(container.firstChild).not.toHaveStyle("will-change: transform;") + expect(container.firstChild).toHaveStyle("will-change: filter;") }) test("Externally-provided motion values are not added to will-change", async () => { const Component = () => { + const willChange = useWillChange() const opacity = useMotionValue(0) const height = useMotionValue(100) - return + return } const { container } = render() - expect(container.firstChild).not.toHaveStyle("will-change: transform;") - }) - - test("Static mode: Doesn't add externally-provided motion values", async () => { - const Component = () => { - const opacity = useMotionValue(0) - const height = useMotionValue(100) - return ( - - - - ) - } - - const { container } = render() - - expect(container.firstChild).not.toHaveStyle("will-change: transform;") expect(container.firstChild).not.toHaveStyle("will-change: opacity;") + expect(container.firstChild).not.toHaveStyle( + "will-change: height, opacity;" + ) + expect(container.firstChild).not.toHaveStyle( + "will-change: opacity, height;" + ) }) test("Don't remove values when they finish animating", async () => { - return new Promise((resolve) => { + return new Promise(async (resolve) => { const Component = () => { + const willChange = useWillChange() return ( { frame.postRender(() => { expect(container.firstChild).toHaveStyle( @@ -157,45 +103,24 @@ describe("willChange", () => { const { container } = render() + await nextFrame() + expect(container.firstChild).toHaveStyle("will-change: transform;") }) }) - test("Doesn't remove transform when some transforms are still animating", async () => { - const Component = ({ animate }: any) => ( - - ) - const { container, rerender } = render() - await nextFrame() - - expect(container.firstChild).not.toHaveStyle("will-change: transform;") - rerender() - - await nextFrame() - await nextFrame() - await nextFrame() - await nextFrame() - await nextFrame() - await nextFrame() - - expect(container.firstChild).toHaveStyle("will-change: transform;") - }) - test("Add values when they start animating", async () => { - const Component = ({ animate }: any) => ( - - ) + const Component = ({ animate }: any) => { + const willChange = useWillChange() + return ( + + ) + } const { container, rerender } = render() await nextFrame() @@ -208,13 +133,17 @@ describe("willChange", () => { }) test("Doesn't remove values when animation interrupted", async () => { - const Component = ({ animate }: any) => ( - - ) + const Component = ({ animate }: any) => { + const willChange = useWillChange() + return ( + + ) + } const { container, rerender } = render() await nextFrame() diff --git a/packages/framer-motion/src/value/use-will-change/add-will-change.ts b/packages/framer-motion/src/value/use-will-change/add-will-change.ts index 41da916ee0..85a75e1de7 100644 --- a/packages/framer-motion/src/value/use-will-change/add-will-change.ts +++ b/packages/framer-motion/src/value/use-will-change/add-will-change.ts @@ -1,6 +1,5 @@ import type { VisualElement } from "../../render/VisualElement" import { isWillChangeMotionValue } from "./is" -import { getWillChangeName } from "./get-will-change-name" export function addValueToWillChange( visualElement: VisualElement, @@ -10,12 +9,11 @@ export function addValueToWillChange( const willChange = visualElement.getValue("willChange") + /** + * It could be that a user has set willChange to a regular MotionValue, + * in which case we can't add the value to it. + */ if (isWillChangeMotionValue(willChange)) { return willChange.add(key) - } else if ( - !visualElement.props.style?.willChange && - getWillChangeName(key) - ) { - visualElement.setStaticValue("willChange", "transform") } }