@shopify/react-native-skia
Version:
High-performance React Native Graphics using Skia
87 lines (80 loc) • 2.16 kB
text/typescript
import type { SpringConfig } from "../types";
import type { TimingConfig } from "../../types";
/**
* @description Returns a cached jsContext function for a spring with duration
* @param mass The mass of the spring
* @param stiffness The stiffness of the spring
* @param damping Spring damping
* @param velocity The initial velocity
*/
export const createSpringEasing = (
params: Partial<SpringConfig>
): TimingConfig => {
const config = {
mass: 1,
stiffness: 100,
damping: 10,
velocity: 0,
...params,
};
config.velocity /= 100;
return getSpringEasing(config);
};
const getSpringEasing = (config: Required<SpringConfig>) => {
const { stiffness, mass, damping, velocity: initialVelocity } = config;
// Setup spring state
const state = {
w0: Math.sqrt(stiffness / mass),
zeta: 0,
wd: 0,
a: 1,
b: 0,
};
state.zeta = damping / (2 * Math.sqrt(stiffness * mass));
state.wd =
state.zeta < 1 ? state.w0 * Math.sqrt(1 - state.zeta * state.zeta) : 0;
state.a = 1;
state.b =
state.zeta < 1
? (state.zeta * state.w0 + -initialVelocity) / state.wd
: -initialVelocity + state.w0;
const update = (t: number, duration?: number) => {
let progress = duration ? (duration * t) / 1000 : t;
if (state.zeta < 1) {
progress =
Math.exp(-progress * state.zeta * state.w0) *
(state.a * Math.cos(state.wd * progress) +
state.b * Math.sin(state.wd * progress));
} else {
progress =
(state.a + state.b * progress) * Math.exp(-progress * state.w0);
}
if (t === 0 || t === 1) {
return t;
}
return 1 - progress;
};
const getDurationMs = () => {
var frame = 1 / 6;
var elapsed = 0;
var rest = 0;
while (true) {
elapsed += frame;
if (update(elapsed) === 1) {
rest++;
if (rest >= 6) {
break;
}
} else {
rest = 0;
}
}
var durationMs = elapsed * frame * 1000;
return durationMs + 1000;
};
const durationMs = getDurationMs();
return {
easing: (t: number) => update(t, durationMs),
duration: durationMs,
};
};