From 2082539a5643bb62d7e69635e12315df8d591550 Mon Sep 17 00:00:00 2001 From: Josh <37798644+joshuaellis@users.noreply.github.com> Date: Sun, 14 Nov 2021 19:15:26 +0000 Subject: [PATCH] feat: add easing functions to `react-spring` --- .../components/CodeBlock/reactLiveScope.tsx | 2 + docs/src/pages/changelog.mdx | 55 +++++++++++ docs/src/pages/common/configs.mdx | 49 ++++++++- packages/core/src/AnimationConfig.ts | 8 +- packages/core/src/constants.ts | 99 +++++++++++++++++++ 5 files changed, 208 insertions(+), 5 deletions(-) diff --git a/docs/src/components/CodeBlock/reactLiveScope.tsx b/docs/src/components/CodeBlock/reactLiveScope.tsx index 96dbae94c6..399ce557a1 100644 --- a/docs/src/components/CodeBlock/reactLiveScope.tsx +++ b/docs/src/components/CodeBlock/reactLiveScope.tsx @@ -16,6 +16,7 @@ import { Spring, SpringRef, Transition, + easings, } from '@react-spring/web' import { Parallax, ParallaxLayer } from '@react-spring/parallax' import { mdx } from '@mdx-js/react' @@ -80,6 +81,7 @@ const springScope = { Transition, Parallax, ParallaxLayer, + easings, } const reactScope = { diff --git a/docs/src/pages/changelog.mdx b/docs/src/pages/changelog.mdx index e1912c9595..e57c3444e7 100644 --- a/docs/src/pages/changelog.mdx +++ b/docs/src/pages/changelog.mdx @@ -1,5 +1,60 @@ # Changelog +## v9.3.0 + +### Features + +- feat: interpolate bare numbers with units by @CodyJasonBennett in https://github.com/pmndrs/react-spring/pull/1473 + +### Fixes + +- fix: handle ParallaxLayers inside fragments (#1667) by @kindoflew in https://github.com/pmndrs/react-spring/pull/1671 +- fix(native): add children prop by @CodyJasonBennett in https://github.com/pmndrs/react-spring/pull/1705 + +## v9.2.6 + +### Fixes + +- useChain does not run hooks in sequence when duration is specified #1492 +- useSpring does not orchestrate animations if one is using config w/o duration, when others do #1584 +- Type inference failed with functions passed to useSpring etc. + +## v9.2.5 + +### Fixes + +- zDog types (#1665) +- Type inference fails when function is used with enter in useTransition (#1483) + +## v9.2.4 + +### Fixes + +- Animated should not try to access Array.prototype (#1585) +- Add `immediate` to payload in useTransition hook (#1600) +- useSprings controller clear refs properly when length changes in React.StrictMode (#1597) + +## v9.2.3 + +### Fixes + +- incorrect type imports for rafz (#1560) + +## v9.2.2 + +### Features + +- NEW package @react-spring/rafz (a fork of the pmndrs library rafz) – did not constitute minor bump. + +### Fixes + +- Three – Array props were not been updated correctly, big thanks @midanosi! (#1430) +- Three – XR session was breaking Springs, big thanks @ffdead (#1518) + +### Chores + +- update the yarn.lock with updated packages + ## v9.2.1 ### Fixes diff --git a/docs/src/pages/common/configs.mdx b/docs/src/pages/common/configs.mdx index 99e5d029ef..89d0596f39 100644 --- a/docs/src/pages/common/configs.mdx +++ b/docs/src/pages/common/configs.mdx @@ -31,7 +31,7 @@ And we've added the following properties `frequency`, `damping`, `round`, `bounc | clamp | false | when true, stops the spring once it overshoots its boundaries | | precision | 0.01 | precision | | velocity | 0 | initial velocity (see [v9 changelog](/changelog#changes-in-configvelocity) for a breaking change). | -| easing | t => t | linear by default, you can use any easing you want, for instance d3-ease | +| easing | t => t | linear by default, you can use any easing you want, for instance d3-ease, we have included a variety of easings see [here](#easings) | | damping | 1 | The damping ratio, which dictates how the spring slows down. Only works when `frequency` is defined. Defaults to `1`. | | progress | 0 | When used with `duration`, it decides how far into the easing function to start from. The duration itself is unaffected. | | duration | undefined | if > than 0 will switch to a duration-based animation instead of spring physics, value should be indicated in milliseconds (e.g. `duration: 250` for a duration of 250ms) | @@ -69,3 +69,50 @@ useSpring({ ..., config: config.default }) live url="https://codesandbox.io/embed/react-spring-preset-configs-kdv7r?fontsize=14&hidenavigation=1&theme=dark&view=preview&hidedevtools=1&hidenavigation=1" /> + +## Easings + +Whilst react-spring is supposed to be a spring based animation library, it's evolved over time with the ambition of an all-in-one animation library. + +With this in mind, we now support a multitude of easing functions to be used with the configuration when `duration` is set. + +| In | Out | In Out | +| ------------- | -------------- | ---------------- | +| easeInBack | easeOutBack | easeInOutBack | +| easeInBounce | easeOutBounce | easeInOutBounce | +| easeInCirc | easeOutCirc | easeInOutCirc | +| easeInCubic | easeOutCubic | easeInOutCubic | +| easeInElastic | easeOutElastic | easeInOutElastic | +| easeInExpo | easeOutExpo | easeInOutExpo | +| easeInQuad | easeOutQuad | easeInOutQuad | +| easeInQuart | easeOutQuart | easeInOutQuart | +| easeInQuint | easeOutQuint | easeInOutQuint | +| easeInSine | easeOutSine | easeInOutSine | + +These are used like so: + +```js render=true edit=true +function EasingComponent() { + const { background, rotateZ } = useSpring({ + from: { + background: '#46e891', + rotateZ: 0, + }, + to: { + background: '#277ef4', + rotateZ: 225, + }, + config: { + duration: 2000, + easing: easings.easeInOutQuart, + }, + loop: { reverse: true }, + }) + + return ( + + ) +} +``` diff --git a/packages/core/src/AnimationConfig.ts b/packages/core/src/AnimationConfig.ts index 913a74b22f..0ac9189603 100644 --- a/packages/core/src/AnimationConfig.ts +++ b/packages/core/src/AnimationConfig.ts @@ -1,12 +1,12 @@ import { is } from '@react-spring/shared' -import { config as configs } from './constants' +import { EasingFunction } from '@react-spring/types' +import { config as configs, easings } from './constants' -const linear = (t: number) => t const defaults: any = { ...configs.default, mass: 1, damping: 1, - easing: linear, + easing: easings.linear, clamp: false, } @@ -100,7 +100,7 @@ export class AnimationConfig { * * Defaults to quadratic ease-in-out. */ - easing!: (t: number) => number + easing!: EasingFunction /** * Avoid overshooting by ending abruptly at the goal value. diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index 39a2833ec7..4c1be8a918 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -1,3 +1,5 @@ +import { EasingFunction } from '@react-spring/types' + // The `mass` prop defaults to 1 export const config = { default: { tension: 170, friction: 26 }, @@ -7,3 +9,100 @@ export const config = { slow: { tension: 280, friction: 60 }, molasses: { tension: 280, friction: 120 }, } as const + +/** + * With thanks to ai easings.net + * https://github.com/ai/easings.net/blob/master/src/easings/easingsFunctions.ts + */ +interface EasingDictionary { + [easing: string]: EasingFunction +} + +const c1 = 1.70158 +const c2 = c1 * 1.525 +const c3 = c1 + 1 +const c4 = (2 * Math.PI) / 3 +const c5 = (2 * Math.PI) / 4.5 + +const bounceOut: EasingFunction = x => { + const n1 = 7.5625 + const d1 = 2.75 + + if (x < 1 / d1) { + return n1 * x * x + } else if (x < 2 / d1) { + return n1 * (x -= 1.5 / d1) * x + 0.75 + } else if (x < 2.5 / d1) { + return n1 * (x -= 2.25 / d1) * x + 0.9375 + } else { + return n1 * (x -= 2.625 / d1) * x + 0.984375 + } +} + +export const easings: EasingDictionary = { + linear: x => x, + easeInQuad: x => x * x, + easeOutQuad: x => 1 - (1 - x) * (1 - x), + easeInOutQuad: x => (x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2), + easeInCubic: x => x * x * x, + easeOutCubic: x => 1 - Math.pow(1 - x, 3), + easeInOutCubic: x => + x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2, + easeInQuart: x => x * x * x * x, + easeOutQuart: x => 1 - Math.pow(1 - x, 4), + easeInOutQuart: x => + x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2, + easeInQuint: x => x * x * x * x * x, + easeOutQuint: x => 1 - Math.pow(1 - x, 5), + easeInOutQuint: x => + x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2, + easeInSine: x => 1 - Math.cos((x * Math.PI) / 2), + easeOutSine: x => Math.sin((x * Math.PI) / 2), + easeInOutSine: x => -(Math.cos(Math.PI * x) - 1) / 2, + easeInExpo: x => (x === 0 ? 0 : Math.pow(2, 10 * x - 10)), + easeOutExpo: x => (x === 1 ? 1 : 1 - Math.pow(2, -10 * x)), + easeInOutExpo: x => + x === 0 + ? 0 + : x === 1 + ? 1 + : x < 0.5 + ? Math.pow(2, 20 * x - 10) / 2 + : (2 - Math.pow(2, -20 * x + 10)) / 2, + easeInCirc: x => 1 - Math.sqrt(1 - Math.pow(x, 2)), + easeOutCirc: x => Math.sqrt(1 - Math.pow(x - 1, 2)), + easeInOutCirc: x => + x < 0.5 + ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 + : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2, + easeInBack: x => c3 * x * x * x - c1 * x * x, + easeOutBack: x => 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2), + easeInOutBack: x => + x < 0.5 + ? (Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2 + : (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2, + easeInElastic: x => + x === 0 + ? 0 + : x === 1 + ? 1 + : -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * c4), + easeOutElastic: x => + x === 0 + ? 0 + : x === 1 + ? 1 + : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1, + easeInOutElastic: x => + x === 0 + ? 0 + : x === 1 + ? 1 + : x < 0.5 + ? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + : (Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + 1, + easeInBounce: x => 1 - bounceOut(1 - x), + easeOutBounce: bounceOut, + easeInOutBounce: x => + x < 0.5 ? (1 - bounceOut(1 - 2 * x)) / 2 : (1 + bounceOut(2 * x - 1)) / 2, +} as const