Skip to content

Commit

Permalink
Adding support for all easing types with WAAPI (#1830)
Browse files Browse the repository at this point in the history
* Adding support for back/circ easings to WAAPI

* Updating

* Updating changelog

* Fixing logic

* Updating test

* Pregenerating keyframes for unsupported easing functions

* Fixing test

* Renaming supported easing

* Adding comment
  • Loading branch information
mattgperry committed Dec 16, 2022
1 parent 721ec8e commit 4fc0790
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 55 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,12 @@ Framer Motion adheres to [Semantic Versioning](http://semver.org/).

Undocumented APIs should be considered internal and may change without warning.

## [7.10.2] 2022-12-16

### Fixed

- Adding support for all easing functions with WAAPI.

## [7.10.1] 2022-12-16

### Fixed
Expand Down
1 change: 0 additions & 1 deletion packages/framer-motion/src/animation/index.ts
Expand Up @@ -133,7 +133,6 @@ export const createMotionValueAnimation = (
!options.repeatDelay &&
options.repeatType !== "mirror" &&
options.damping !== 0 &&
typeof options.ease !== "function" &&
visualElement &&
element instanceof HTMLElement &&
!visualElement.getProps().onUpdate
Expand Down
Expand Up @@ -166,7 +166,7 @@ export function animate<V = number>({
driverControls.stop()
},
sample: (t: number) => {
return animation.next(Math.max(0, t)).value
return animation.next(Math.max(0, t))
},
}
}
Expand Up @@ -3,9 +3,9 @@ import { sync } from "../../frameloop"
import type { VisualElement } from "../../render/VisualElement"
import type { MotionValue } from "../../value"
import { animate } from "../legacy-popmotion"
import { spring } from "../legacy-popmotion/spring"
import { AnimationOptions } from "../types"
import { animateStyle } from "./"
import { isWaapiSupportedEasing } from "./easing"

/**
* 10ms is chosen here as it strikes a balance between smooth
Expand All @@ -22,25 +22,21 @@ export function createAcceleratedAnimation(
let { keyframes, duration = 0.3, elapsed = 0, ease } = options

/**
* If this is a spring animation, pre-generate keyframes and
* record duration.
*
* TODO: When introducing support for values beyond opacity it
* might be better to use `animate.sample()`
* If this animation needs pre-generated keyframes then generate.
*/
if (options.type === "spring") {
const springAnimation = spring(options)
if (options.type === "spring" || !isWaapiSupportedEasing(options.ease)) {
const sampleAnimation = animate(options)
let state = { done: false, value: keyframes[0] }
const springKeyframes: number[] = []
const pregeneratedKeyframes: number[] = []

let t = 0
while (!state.done) {
state = springAnimation.next(t)
springKeyframes.push(state.value)
state = sampleAnimation.sample(t)
pregeneratedKeyframes.push(state.value)
t += sampleDelta
}

keyframes = springKeyframes
keyframes = pregeneratedKeyframes
duration = t - sampleDelta
ease = "linear"
}
Expand Down Expand Up @@ -94,8 +90,8 @@ export function createAcceleratedAnimation(
if (currentTime) {
const sampleAnimation = animate(options)
value.setWithVelocity(
sampleAnimation.sample(currentTime - sampleDelta),
sampleAnimation.sample(currentTime),
sampleAnimation.sample(currentTime - sampleDelta).value,
sampleAnimation.sample(currentTime).value,
sampleDelta
)
}
Expand Down
33 changes: 20 additions & 13 deletions packages/framer-motion/src/animation/waapi/easing.ts
@@ -1,19 +1,26 @@
import { BezierDefinition, EasingDefinition } from "../../easing/types"
import { camelToDash } from "../../render/dom/utils/camel-to-dash"
import { BezierDefinition, Easing, EasingDefinition } from "../../easing/types"

export function isWaapiSupportedEasing(easing?: Easing | Easing[]) {
return (
!easing || // Default easing
Array.isArray(easing) || // Bezier curve
(typeof easing === "string" && supportedWaapiEasing[easing])
)
}

export const cubicBezierAsString = ([a, b, c, d]: BezierDefinition) =>
`cubic-bezier(${a}, ${b}, ${c}, ${d})`

const validWaapiEasing = new Set([
"linear",
"ease-in",
"ease-out",
"ease-in-out",
])

export function mapEasingName(easingName: string): string {
const name = camelToDash(easingName)
return validWaapiEasing.has(name) ? name : "ease"
export const supportedWaapiEasing = {
linear: "linear",
ease: "ease",
easeIn: "ease-in",
easeOut: "ease-out",
easeInOut: "ease-in-out",
circIn: cubicBezierAsString([0, 0.65, 0.55, 1]),
circOut: cubicBezierAsString([0.55, 0, 1, 0.45]),
backIn: cubicBezierAsString([0.31, 0.01, 0.66, -0.59]),
backOut: cubicBezierAsString([0.33, 1.53, 0.69, 0.99]),
}

export function mapEasingToNativeEasing(
Expand All @@ -22,5 +29,5 @@ export function mapEasingToNativeEasing(
if (!easing) return undefined
return Array.isArray(easing)
? cubicBezierAsString(easing)
: mapEasingName(easing)
: supportedWaapiEasing[easing]
}
14 changes: 3 additions & 11 deletions packages/framer-motion/src/easing/anticipate.ts
@@ -1,12 +1,4 @@
import { createBackIn } from "./back"
import { EasingFunction } from "./types"
import { backIn } from "./back"

const createAnticipate = (power?: number): EasingFunction => {
const backEasing = createBackIn(power)
return (p) =>
(p *= 2) < 1
? 0.5 * backEasing(p)
: 0.5 * (2 - Math.pow(2, -10 * (p - 1)))
}

export const anticipate = createAnticipate()
export const anticipate = (p: number) =>
(p *= 2) < 1 ? 0.5 * backIn(p) : 0.5 * (2 - Math.pow(2, -10 * (p - 1)))
11 changes: 3 additions & 8 deletions packages/framer-motion/src/easing/back.ts
@@ -1,12 +1,7 @@
import { cubicBezier } from "./cubic-bezier"
import { mirrorEasing } from "./modifiers/mirror"
import { reverseEasing } from "./modifiers/reverse"
import { EasingFunction } from "./types"

export const createBackIn =
(power: number = 1.525): EasingFunction =>
(p) =>
p * p * ((power + 1) * p - power)

export const backIn = createBackIn()
export const backOut = reverseEasing(backIn)
export const backOut = cubicBezier(0.33, 1.53, 0.69, 0.99)
export const backIn = reverseEasing(backOut)
export const backInOut = mirrorEasing(backIn)

0 comments on commit 4fc0790

Please sign in to comment.