diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a5dd41d92..0157c16f2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ Undocumented APIs should be considered internal and may change without warning. ## [10.0.0] 2023-02-24 +### Added + +- `background-color` animations are now hardware accelerated. + ### Removed - Removing fallback for `IntersectionObserver`. Use a polyfill for support in older browsers. @@ -17,6 +21,10 @@ Undocumented APIs should be considered internal and may change without warning. - Using `value.onChange` will now throw a warning with instructions to change to `value.on("change", callback)`. - Using `AnimateSharedLayout` now throws an error. +### Fixed + +- `repeat: Infinity` no longer de-opts from pre-generated WAAPI animations. + ## [9.1.7] 2023-02-24 ### Fixed diff --git a/dev/package.json b/dev/package.json index 1f17552d85..3c051fcc84 100644 --- a/dev/package.json +++ b/dev/package.json @@ -1,6 +1,6 @@ { "name": "framer-motion--dev", - "version": "10.0.0", + "version": "10.0.0-alpha.1", "private": true, "scripts": { "dev": "webpack serve --config ./webpack/config.js --hot" @@ -8,8 +8,8 @@ "dependencies": { "@react-three/drei": "^7.27.3", "@react-three/fiber": "^8.2.2", - "framer-motion": "^10.0.0", - "framer-motion-3d": "^10.0.0", + "framer-motion": "^10.0.0-alpha.1", + "framer-motion-3d": "^10.0.0-alpha.1", "path-browserify": "^1.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/lerna.json b/lerna.json index a693ab314a..a9233f9d2e 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "10.0.0", + "version": "10.0.0-alpha.1", "packages": [ "packages/*" ], diff --git a/packages/framer-motion-3d/package.json b/packages/framer-motion-3d/package.json index e3220ca708..6ab010c14f 100644 --- a/packages/framer-motion-3d/package.json +++ b/packages/framer-motion-3d/package.json @@ -1,6 +1,6 @@ { "name": "framer-motion-3d", - "version": "10.0.0", + "version": "10.0.0-alpha.1", "description": "A simple and powerful React animation library for @react-three/fiber", "main": "dist/cjs/index.js", "module": "dist/es/index.mjs", @@ -46,7 +46,7 @@ "postpublish": "git push --tags" }, "dependencies": { - "framer-motion": "^10.0.0", + "framer-motion": "^10.0.0-alpha.1", "react-merge-refs": "^2.0.1" }, "peerDependencies": { diff --git a/packages/framer-motion/package.json b/packages/framer-motion/package.json index 0676aff93c..2f459b3c3c 100644 --- a/packages/framer-motion/package.json +++ b/packages/framer-motion/package.json @@ -1,6 +1,6 @@ { "name": "framer-motion", - "version": "10.0.0", + "version": "10.0.0-alpha.1", "description": "A simple and powerful React animation library", "main": "dist/cjs/index.js", "module": "dist/es/index.mjs", @@ -70,7 +70,7 @@ "bundlesize": [ { "path": "./dist/size-rollup-motion.js", - "maxSize": "29.92 kB" + "maxSize": "29.88 kB" }, { "path": "./dist/size-rollup-m.js", @@ -78,11 +78,11 @@ }, { "path": "./dist/size-rollup-dom-animation.js", - "maxSize": "14.85 kB" + "maxSize": "14.75 kB" }, { "path": "./dist/size-rollup-dom-max.js", - "maxSize": "25.67 kB" + "maxSize": "25.61 kB" }, { "path": "./dist/size-webpack-m.js", @@ -90,11 +90,11 @@ }, { "path": "./dist/size-webpack-dom-animation.js", - "maxSize": "18.81 kB" + "maxSize": "18.75 kB" }, { "path": "./dist/size-webpack-dom-max.js", - "maxSize": "30.52 kB" + "maxSize": "30.47 kB" } ], "gitHead": "79675ba44230ae86bfb212be8bd903fc68524976" diff --git a/packages/framer-motion/src/animation/waapi/create-accelerated-animation.ts b/packages/framer-motion/src/animation/waapi/create-accelerated-animation.ts index cb98bea333..7af8329323 100644 --- a/packages/framer-motion/src/animation/waapi/create-accelerated-animation.ts +++ b/packages/framer-motion/src/animation/waapi/create-accelerated-animation.ts @@ -17,6 +17,7 @@ const acceleratedValues = new Set<string>([ "clipPath", "filter", "transform", + "backgroundColor", ]) /** @@ -26,6 +27,14 @@ const acceleratedValues = new Set<string>([ */ const sampleDelta = 10 //ms +const requiresPregeneratedKeyframes = ( + valueName: string, + options: AnimationOptions +) => + options.type === "spring" || + valueName === "backgroundColor" || + !isWaapiSupportedEasing(options.ease) + export function createAcceleratedAnimation( value: MotionValue, valueName: string, @@ -45,14 +54,12 @@ export function createAcceleratedAnimation( /** * If this animation needs pre-generated keyframes then generate. */ - if (options.type === "spring" || !isWaapiSupportedEasing(options.ease)) { - /** - * If we need to pre-generate keyframes and repeat is infinite then - * early return as this will lock the thread. - */ - if (options.repeat === Infinity) return - - const sampleAnimation = animateValue({ ...options, elapsed: 0 }) + if (requiresPregeneratedKeyframes(valueName, options)) { + const sampleAnimation = animateValue({ + ...options, + repeat: 0, + elapsed: 0, + }) let state = { done: false, value: keyframes[0] } const pregeneratedKeyframes: number[] = [] diff --git a/packages/framer-motion/src/motion/__tests__/waapi.test.tsx b/packages/framer-motion/src/motion/__tests__/waapi.test.tsx index e932468849..694ddbb854 100644 --- a/packages/framer-motion/src/motion/__tests__/waapi.test.tsx +++ b/packages/framer-motion/src/motion/__tests__/waapi.test.tsx @@ -144,6 +144,67 @@ describe("WAAPI animations", () => { ) }) + test("backgroundColor animates with WAAPI at default settings", () => { + const ref = createRef<HTMLDivElement>() + const Component = () => ( + <motion.div + ref={ref} + initial={{ backgroundColor: "#f00" }} + animate={{ backgroundColor: "#00f" }} + /> + ) + const { rerender } = render(<Component />) + rerender(<Component />) + + expect(ref.current!.animate).toBeCalled() + expect(ref.current!.animate).toBeCalledWith( + { + backgroundColor: [ + "rgba(255, 0, 0, 1)", + "rgba(253, 0, 35, 1)", + "rgba(249, 0, 56, 1)", + "rgba(244, 0, 75, 1)", + "rgba(237, 0, 94, 1)", + "rgba(229, 0, 112, 1)", + "rgba(220, 0, 128, 1)", + "rgba(211, 0, 144, 1)", + "rgba(200, 0, 158, 1)", + "rgba(190, 0, 171, 1)", + "rgba(179, 0, 182, 1)", + "rgba(168, 0, 192, 1)", + "rgba(157, 0, 201, 1)", + "rgba(147, 0, 209, 1)", + "rgba(136, 0, 215, 1)", + "rgba(126, 0, 222, 1)", + "rgba(116, 0, 227, 1)", + "rgba(107, 0, 232, 1)", + "rgba(97, 0, 236, 1)", + "rgba(88, 0, 239, 1)", + "rgba(79, 0, 242, 1)", + "rgba(71, 0, 245, 1)", + "rgba(62, 0, 247, 1)", + "rgba(54, 0, 249, 1)", + "rgba(46, 0, 251, 1)", + "rgba(38, 0, 252, 1)", + "rgba(30, 0, 253, 1)", + "rgba(22, 0, 254, 1)", + "rgba(15, 0, 255, 1)", + "rgba(7, 0, 255, 1)", + "rgba(0, 0, 255, 1)", + ], + offset: undefined, + }, + { + delay: -0, + duration: 300, + easing: "linear", + iterations: 1, + direction: "normal", + fill: "both", + } + ) + }) + test("opacity animates with WAAPI when no value is originally provided via initial", () => { const ref = createRef<HTMLDivElement>() const Component = () => ( @@ -750,19 +811,77 @@ describe("WAAPI animations", () => { expect(ref.current!.animate).not.toBeCalled() }) - test("Doesn't animate with WAAPI if repeat is Infinity and we need to generate keyframes", () => { + test("Animates with WAAPI if repeat is defined and we need to generate keyframes", () => { const ref = createRef<HTMLDivElement>() const Component = () => ( <motion.div ref={ref} initial={{ opacity: 0 }} - animate={{ opacity: 0.8 }} - transition={{ repeat: Infinity, type: "spring" }} + animate={{ opacity: 0.9 }} + transition={{ + ease: "backInOut", + duration: 0.05, + repeat: 2, + }} /> ) const { rerender } = render(<Component />) rerender(<Component />) - expect(ref.current!.animate).not.toBeCalled() + expect(ref.current!.animate).toBeCalled() + expect(ref.current!.animate).toBeCalledWith( + { + opacity: [ + 0, -0.038019759996313955, 0.14036703066311026, + 0.7596329693368897, 0.9380197599963139, 0.9, + ], + offset: undefined, + }, + { + delay: -0, + direction: "normal", + duration: 50, + easing: "linear", + fill: "both", + iterations: 3, + } + ) + }) + + test("Animates with WAAPI if repeat is Infinity and we need to generate keyframes", () => { + const ref = createRef<HTMLDivElement>() + const Component = () => ( + <motion.div + ref={ref} + initial={{ opacity: 0 }} + animate={{ opacity: 0.9 }} + transition={{ + ease: "backInOut", + duration: 0.05, + repeat: Infinity, + }} + /> + ) + const { rerender } = render(<Component />) + rerender(<Component />) + + expect(ref.current!.animate).toBeCalled() + expect(ref.current!.animate).toBeCalledWith( + { + opacity: [ + 0, -0.038019759996313955, 0.14036703066311026, + 0.7596329693368897, 0.9380197599963139, 0.9, + ], + offset: undefined, + }, + { + delay: -0, + direction: "normal", + duration: 50, + easing: "linear", + fill: "both", + iterations: Infinity, + } + ) }) }) diff --git a/yarn.lock b/yarn.lock index 4f3e1c62a8..93371ca769 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7844,8 +7844,8 @@ __metadata: cache-loader: ^1.2.5 convert-tsconfig-paths-to-webpack-aliases: ^0.9.2 fork-ts-checker-webpack-plugin: ^6.2.0 - framer-motion: ^10.0.0 - framer-motion-3d: ^10.0.0 + framer-motion: ^10.0.0-alpha.1 + framer-motion-3d: ^10.0.0-alpha.1 path-browserify: ^1.0.1 react: ^18.2.0 react-dom: ^18.2.0 @@ -7911,14 +7911,14 @@ __metadata: languageName: unknown linkType: soft -"framer-motion-3d@^10.0.0, framer-motion-3d@workspace:packages/framer-motion-3d": +"framer-motion-3d@^10.0.0-alpha.1, framer-motion-3d@workspace:packages/framer-motion-3d": version: 0.0.0-use.local resolution: "framer-motion-3d@workspace:packages/framer-motion-3d" dependencies: "@react-three/fiber": ^8.2.2 "@react-three/test-renderer": ^9.0.0 "@rollup/plugin-commonjs": ^22.0.1 - framer-motion: ^10.0.0 + framer-motion: ^10.0.0-alpha.1 react-merge-refs: ^2.0.1 peerDependencies: "@react-three/fiber": ^8.2.2 @@ -7928,7 +7928,7 @@ __metadata: languageName: unknown linkType: soft -"framer-motion@^10.0.0, framer-motion@workspace:packages/framer-motion": +"framer-motion@^10.0.0-alpha.1, framer-motion@workspace:packages/framer-motion": version: 0.0.0-use.local resolution: "framer-motion@workspace:packages/framer-motion" dependencies: