Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add easing functions to react-spring #1752

Merged
merged 1 commit into from
Nov 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/src/components/CodeBlock/reactLiveScope.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -80,6 +81,7 @@ const springScope = {
Transition,
Parallax,
ParallaxLayer,
easings,
}

const reactScope = {
Expand Down
55 changes: 55 additions & 0 deletions docs/src/pages/changelog.mdx
Original file line number Diff line number Diff line change
@@ -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
Expand Down
49 changes: 48 additions & 1 deletion docs/src/pages/common/configs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down Expand Up @@ -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 (
<animated.div
style={{ background, width: 120, height: 120, borderRadius: 16, rotateZ }}
/>
)
}
```
8 changes: 4 additions & 4 deletions packages/core/src/AnimationConfig.ts
Original file line number Diff line number Diff line change
@@ -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,
}

Expand Down Expand Up @@ -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.
Expand Down
99 changes: 99 additions & 0 deletions packages/core/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { EasingFunction } from '@react-spring/types'

// The `mass` prop defaults to 1
export const config = {
default: { tension: 170, friction: 26 },
Expand All @@ -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