@tamagui/react-native-web-lite
Version:
React Native for Web
206 lines (205 loc) • 7.83 kB
JavaScript
import { Animation } from "./Animation.mjs";
import { SpringConfig } from "../SpringConfig.mjs";
import { invariant } from "@tamagui/react-native-web-internals";
import { shouldUseNativeDriver } from "../NativeAnimatedHelper.mjs";
class SpringAnimation extends Animation {
_overshootClamping;
_restDisplacementThreshold;
_restSpeedThreshold;
_lastVelocity;
_startPosition;
_lastPosition;
_fromValue;
_toValue;
_stiffness;
_damping;
_mass;
_initialVelocity;
_delay;
_timeout;
_startTime;
_lastTime;
_frameTime;
_onUpdate;
_animationFrame;
_useNativeDriver;
_platformConfig;
constructor(config) {
super();
this._overshootClamping = config.overshootClamping ?? false;
this._restDisplacementThreshold = config.restDisplacementThreshold ?? 1e-3;
this._restSpeedThreshold = config.restSpeedThreshold ?? 1e-3;
this._initialVelocity = config.velocity ?? 0;
this._lastVelocity = config.velocity ?? 0;
this._toValue = config.toValue;
this._delay = config.delay ?? 0;
this._useNativeDriver = shouldUseNativeDriver(config);
this._platformConfig = config.platformConfig;
this.__isInteraction = config.isInteraction ?? !this._useNativeDriver;
this.__iterations = config.iterations ?? 1;
if (config.stiffness !== void 0 || config.damping !== void 0 || config.mass !== void 0) {
invariant(config.bounciness === void 0 && config.speed === void 0 && config.tension === void 0 && config.friction === void 0, "You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one");
this._stiffness = config.stiffness ?? 100;
this._damping = config.damping ?? 10;
this._mass = config.mass ?? 1;
} else if (config.bounciness !== void 0 || config.speed !== void 0) {
invariant(config.tension === void 0 && config.friction === void 0 && config.stiffness === void 0 && config.damping === void 0 && config.mass === void 0, "You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one");
const springConfig = SpringConfig.fromBouncinessAndSpeed(config.bounciness ?? 8, config.speed ?? 12);
this._stiffness = springConfig.stiffness;
this._damping = springConfig.damping;
this._mass = 1;
} else {
const springConfig = SpringConfig.fromOrigamiTensionAndFriction(config.tension ?? 40, config.friction ?? 7);
this._stiffness = springConfig.stiffness;
this._damping = springConfig.damping;
this._mass = 1;
}
invariant(this._stiffness > 0, "Stiffness value must be greater than 0");
invariant(this._damping > 0, "Damping value must be greater than 0");
invariant(this._mass > 0, "Mass value must be greater than 0");
}
__getNativeAnimationConfig() {
return {
type: "spring",
overshootClamping: this._overshootClamping,
restDisplacementThreshold: this._restDisplacementThreshold,
restSpeedThreshold: this._restSpeedThreshold,
stiffness: this._stiffness,
damping: this._damping,
mass: this._mass,
initialVelocity: this._initialVelocity ?? this._lastVelocity,
toValue: this._toValue,
iterations: this.__iterations,
platformConfig: this._platformConfig
};
}
start(fromValue, onUpdate, onEnd, previousAnimation, animatedValue) {
this.__active = true;
this._startPosition = fromValue;
this._lastPosition = this._startPosition;
this._onUpdate = onUpdate;
this.__onEnd = onEnd;
this._lastTime = Date.now();
this._frameTime = 0;
if (previousAnimation instanceof SpringAnimation) {
const internalState = previousAnimation.getInternalState();
this._lastPosition = internalState.lastPosition;
this._lastVelocity = internalState.lastVelocity;
this._initialVelocity = this._lastVelocity;
this._lastTime = internalState.lastTime;
}
const start = () => {
if (this._useNativeDriver) {
this.__startNativeAnimation(animatedValue);
} else {
this.onUpdate();
}
};
if (this._delay) {
this._timeout = setTimeout(start, this._delay);
} else {
start();
}
}
getInternalState() {
return {
lastPosition: this._lastPosition,
lastVelocity: this._lastVelocity,
lastTime: this._lastTime
};
}
/**
* This spring model is based off of a damped harmonic oscillator
* (https://en.wikipedia.org/wiki/Harmonic_oscillator#Damped_harmonic_oscillator).
*
* We use the closed form of the second order differential equation:
*
* x'' + (2ζ⍵_0)x' + ⍵^2x = 0
*
* where
* ⍵_0 = √(k / m) (undamped angular frequency of the oscillator),
* ζ = c / 2√mk (damping ratio),
* c = damping constant
* k = stiffness
* m = mass
*
* The derivation of the closed form is described in detail here:
* http://planetmath.org/sites/default/files/texpdf/39745.pdf
*
* This algorithm happens to match the algorithm used by CASpringAnimation,
* a QuartzCore (iOS) API that creates spring animations.
*/
onUpdate() {
const MAX_STEPS = 64;
let now = Date.now();
if (now > this._lastTime + MAX_STEPS) {
now = this._lastTime + MAX_STEPS;
}
const deltaTime = (now - this._lastTime) / 1e3;
this._frameTime += deltaTime;
const c = this._damping;
const m = this._mass;
const k = this._stiffness;
const v0 = -this._initialVelocity;
const zeta = c / (2 * Math.sqrt(k * m));
const omega0 = Math.sqrt(k / m);
const omega1 = omega0 * Math.sqrt(1 - zeta * zeta);
const x0 = this._toValue - this._startPosition;
let position = 0;
let velocity = 0;
const t = this._frameTime;
if (zeta < 1) {
const envelope = Math.exp(-zeta * omega0 * t);
position = this._toValue - envelope * ((v0 + zeta * omega0 * x0) / omega1 * Math.sin(omega1 * t) + x0 * Math.cos(omega1 * t));
velocity = zeta * omega0 * envelope * (Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0) / omega1 + x0 * Math.cos(omega1 * t)) - envelope * (Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0) - omega1 * x0 * Math.sin(omega1 * t));
} else {
const envelope = Math.exp(-omega0 * t);
position = this._toValue - envelope * (x0 + (v0 + omega0 * x0) * t);
velocity = envelope * (v0 * (t * omega0 - 1) + t * x0 * (omega0 * omega0));
}
this._lastTime = now;
this._lastPosition = position;
this._lastVelocity = velocity;
this._onUpdate(position);
if (!this.__active) {
return;
}
let isOvershooting = false;
if (this._overshootClamping && this._stiffness !== 0) {
if (this._startPosition < this._toValue) {
isOvershooting = position > this._toValue;
} else {
isOvershooting = position < this._toValue;
}
}
const isVelocity = Math.abs(velocity) <= this._restSpeedThreshold;
let isDisplacement = true;
if (this._stiffness !== 0) {
isDisplacement = Math.abs(this._toValue - position) <= this._restDisplacementThreshold;
}
if (isOvershooting || isVelocity && isDisplacement) {
if (this._stiffness !== 0) {
this._lastPosition = this._toValue;
this._lastVelocity = 0;
this._onUpdate(this._toValue);
}
this.__debouncedOnEnd({
finished: true
});
return;
}
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
}
stop() {
super.stop();
this.__active = false;
clearTimeout(this._timeout);
global.cancelAnimationFrame(this._animationFrame);
this.__debouncedOnEnd({
finished: false
});
}
}
var SpringAnimation_default = SpringAnimation;
export { SpringAnimation, SpringAnimation_default as default };
//# sourceMappingURL=SpringAnimation.mjs.map