addimated
Version:
An always interruptable, declarative animation library for React
203 lines (173 loc) • 8.29 kB
JavaScript
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
import _createClass from "@babel/runtime/helpers/esm/createClass";
import _possibleConstructorReturn from "@babel/runtime/helpers/esm/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/esm/getPrototypeOf";
import _inherits from "@babel/runtime/helpers/esm/inherits";
import invariant from "invariant";
import { Animation } from "./Animation";
import { fromBouncinessAndSpeed, fromOrigamiTensionAndFriction } from "./SpringConfig";
import { withDefault } from "./WithDefault";
var SpringAnimation =
/*#__PURE__*/
function (_Animation) {
_inherits(SpringAnimation, _Animation);
function SpringAnimation(config) {
var _this;
_classCallCheck(this, SpringAnimation);
_this = _possibleConstructorReturn(this, _getPrototypeOf(SpringAnimation).call(this));
_this.overshootClamping = withDefault(config.overshootClamping, false);
_this.restDisplacementThreshold = withDefault(config.restDisplacementThreshold, 0.001);
_this.restSpeedThreshold = withDefault(config.restSpeedThreshold, 0.001);
_this.initialVelocity = withDefault(config.velocity, NaN);
_this.lastVelocity = withDefault(config.velocity, NaN);
_this.toValue = config.toValue;
_this.delay = withDefault(config.delay, 0);
if (config.stiffness !== undefined || config.damping !== undefined || config.mass !== undefined) {
!(config.bounciness === undefined && config.speed === undefined && config.tension === undefined && config.friction === undefined) ? process.env.NODE_ENV !== "production" ? invariant(false, "You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one") : invariant(false) : void 0;
_this.stiffness = withDefault(config.stiffness, 100);
_this.damping = withDefault(config.damping, 10);
_this.mass = withDefault(config.mass, 1);
} else if (config.bounciness !== undefined || config.speed !== undefined) {
// Convert the origami bounciness/speed values to stiffness/damping
// We assume mass is 1.
!(config.tension === undefined && config.friction === undefined && config.stiffness === undefined && config.damping === undefined && config.mass === undefined) ? process.env.NODE_ENV !== "production" ? invariant(false, "You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one") : invariant(false) : void 0;
var springConfig = fromBouncinessAndSpeed(withDefault(config.bounciness, 8), withDefault(config.speed, 12));
_this.stiffness = springConfig.stiffness;
_this.damping = springConfig.damping;
_this.mass = 1;
} else {
// Convert the origami tension/friction values to stiffness/damping
// We assume mass is 1.
var _springConfig = fromOrigamiTensionAndFriction(withDefault(config.tension, 40), withDefault(config.friction, 7));
_this.stiffness = _springConfig.stiffness;
_this.damping = _springConfig.damping;
_this.mass = 1;
}
!(_this.stiffness > 0) ? process.env.NODE_ENV !== "production" ? invariant(false, "Stiffness value must be greater than 0") : invariant(false) : void 0;
!(_this.damping > 0) ? process.env.NODE_ENV !== "production" ? invariant(false, "Damping value must be greater than 0") : invariant(false) : void 0;
!(_this.mass > 0) ? process.env.NODE_ENV !== "production" ? invariant(false, "Mass value must be greater than 0") : invariant(false) : void 0;
return _this;
}
_createClass(SpringAnimation, [{
key: "getInternalState",
value: function getInternalState() {
return {
lastPosition: this.lastPosition,
lastVelocity: this.lastVelocity,
lastTime: this.lastTime
};
}
}, {
key: "nextFrame",
value: function nextFrame(now) {
// TODO: Rethink delay handling here
if (now <= this.lastTime) return [this.startPosition, false];
var deltaTime = (now - this.lastTime) / 1000;
this.frameTime += deltaTime;
var c = this.damping;
var m = this.mass;
var k = this.stiffness;
var v0 = -this.initialVelocity;
var zeta = c / (2 * Math.sqrt(k * m)); // damping ratio
var omega0 = Math.sqrt(k / m); // undamped angular frequency of the oscillator (rad/ms)
var omega1 = omega0 * Math.sqrt(1.0 - zeta * zeta); // exponential decay
var x0 = this.toValue - this.startPosition; // calculate the oscillation from x0 = 1 to x = 0
var position = 0.0;
var velocity = 0.0;
var t = this.frameTime;
if (zeta < 1) {
// Under damped
var envelope = Math.exp(-zeta * omega0 * t);
position = this.toValue - envelope * ((v0 + zeta * omega0 * x0) / omega1 * Math.sin(omega1 * t) + x0 * Math.cos(omega1 * t)); // This looks crazy -- it's actually just the derivative of the
// oscillation function
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 {
// Critically damped
var _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; // Conditions for stopping the spring animation
var finished = false;
var isOvershooting = false;
if (this.overshootClamping && this.stiffness !== 0) {
if (this.startPosition < this.toValue) {
isOvershooting = position > this.toValue;
} else {
isOvershooting = position < this.toValue;
}
}
var isVelocity = Math.abs(velocity) <= this.restSpeedThreshold;
var isDisplacement = true;
if (this.stiffness !== 0) {
isDisplacement = Math.abs(this.toValue - position) <= this.restDisplacementThreshold;
}
if (isOvershooting || isVelocity && isDisplacement) {
if (this.stiffness !== 0) {
// Ensure that we end up with a round value
this.lastPosition = this.toValue;
this.lastVelocity = 0;
position = this.toValue;
}
finished = true;
}
return [position, finished];
}
}, {
key: "start",
value: function start(animatedVal, _fromValue, onEnd) {
var currentVal = animatedVal.__getValue();
animatedVal.model = this.toValue;
this.active = true;
this.fromValue = currentVal - this.toValue;
this.toValue = 0;
this.endCallback = onEnd;
if (isNaN(this.initialVelocity) || isNaN(this.lastVelocity)) {
var velocity = animatedVal.velocity != null ? animatedVal.velocity * 1000 : 0;
this.initialVelocity = velocity;
this.lastVelocity = velocity;
}
this.startPosition = this.fromValue;
this.lastPosition = this.startPosition;
this.currentValue = currentVal;
this.lastTime = performance.now() + this.delay;
this.frameTime = 0;
animatedVal.animations.forEach(function (anim) {
return anim.stop(false);
});
return [this];
}
}, {
key: "step",
value: function step(timestamp) {
var _this$nextFrame = this.nextFrame(timestamp),
_this$nextFrame2 = _slicedToArray(_this$nextFrame, 2),
currentValue = _this$nextFrame2[0],
finished = _this$nextFrame2[1];
this.currentValue = currentValue;
if (finished) {
this.stop(true);
}
}
}, {
key: "getValue",
value: function getValue() {
return this.currentValue;
}
}, {
key: "stop",
value: function stop() {
var finished = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
this.ended = true;
this.endCallback && this.endCallback({
finished: finished
});
}
}]);
return SpringAnimation;
}(Animation);
export { SpringAnimation };
//# sourceMappingURL=SpringAnimation.js.map