vevet
Version:
Vevet is a JavaScript library for creative development that simplifies crafting rich interactions like split text animations, carousels, marquees, preloading, and more.
254 lines • 11.3 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SwipeInertia = void 0;
var Raf_1 = require("../../../components/Raf");
var isFiniteNumber_1 = require("../../../internal/isFiniteNumber");
var utils_1 = require("../../../utils");
var IDLE_VEC3 = { x: 0, y: 0, angle: 0 };
var IDLE_STATE = __assign(__assign({}, IDLE_VEC3), { time: 0 });
var LERP_APPROX = 0.01;
var BELOW_THRESHOLD = 0.1;
var SwipeInertia = /** @class */ (function () {
function SwipeInertia(ctx) {
this.ctx = ctx;
this._velocity = __assign({}, IDLE_STATE);
this._initialVelocity = __assign({}, IDLE_STATE);
this._saveRawMovement = __assign({}, IDLE_VEC3);
this._rawMovement = __assign({}, IDLE_VEC3);
this._saveStep = __assign({}, IDLE_STATE);
this._saveCurrent = __assign({}, IDLE_STATE);
}
Object.defineProperty(SwipeInertia.prototype, "has", {
/** Check if inertia is active */
get: function () {
return !!this._raf;
},
enumerable: false,
configurable: true
});
/** Apply inertia-based movement */
SwipeInertia.prototype.release = function (onUpdate) {
var ctx = this.ctx;
var props = ctx.props;
this._modifiedDistance = undefined;
this._saveCurrent = __assign({}, ctx.coords.current);
this._saveStep = __assign({}, ctx.coords.step);
this._saveRawMovement = __assign({}, ctx.coords.rawMovement);
this._rawMovement = __assign({}, ctx.coords.rawMovement);
var data = this._calcVelocity();
if (!data || !(0, isFiniteNumber_1.isFiniteNumber)(data.dt) || data.dt <= 0) {
ctx.onFail();
return false;
}
var linearSpeed = data.linearSpeed, angularSpeed = data.angularSpeed, vx = data.vx, vy = data.vy, va = data.va, threshold = data.threshold;
if (!(0, isFiniteNumber_1.isFiniteNumber)(linearSpeed) ||
!(0, isFiniteNumber_1.isFiniteNumber)(angularSpeed) ||
(linearSpeed < threshold && angularSpeed < threshold)) {
ctx.onFail();
return false;
}
this._onUpdate = onUpdate;
this._velocity = { x: vx, y: vy, angle: va, time: performance.now() };
this._initialVelocity = __assign({}, this._velocity);
if (props.inertiaDistanceModifier) {
this._modifiedDistance = props.inertiaDistanceModifier({
x: this._predictDistance(vx, props.inertiaDecay),
y: this._predictDistance(vy, props.inertiaDecay),
angle: this._predictDistance(va, props.inertiaDecay),
});
}
this._raf = new Raf_1.Raf({
enabled: true,
onFrame: this._handleRaf.bind(this),
});
this.ctx.onStart();
return true;
};
/** Calculate velocity */
SwipeInertia.prototype._calcVelocity = function () {
var _a = this, current = _a._saveCurrent, step = _a._saveStep;
var _b = this.ctx.props, inertiaRatio = _b.inertiaRatio, ratio = _b.ratio, maxVelocity = _b.maxVelocity, props = __rest(_b, ["inertiaRatio", "ratio", "maxVelocity"]);
if (!current || !step) {
return null;
}
var gap = performance.now() - current.time;
var dt = Math.max(step.time, gap, 1);
var iRatio = (0, isFiniteNumber_1.isFiniteNumber)(inertiaRatio) ? inertiaRatio : 1;
var sRatio = (0, isFiniteNumber_1.isFiniteNumber)(ratio) ? ratio : 1;
var finalRatio = sRatio * iRatio;
var maxVX = maxVelocity.x ? Math.abs(maxVelocity.x) : 0;
var vx = (step.x / dt) * finalRatio;
vx = (0, utils_1.clamp)(vx, -maxVX, maxVX);
var maxVY = maxVelocity.y ? Math.abs(maxVelocity.y) : 0;
var vy = (step.y / dt) * finalRatio;
vy = (0, utils_1.clamp)(vy, -maxVY, maxVY);
var maxVA = maxVelocity.angle ? Math.abs(maxVelocity.angle) : 0;
var va = (step.angle / dt) * finalRatio;
va = (0, utils_1.clamp)(va, -maxVA, maxVA);
var linearSpeed = Math.hypot(vx, vy) * 1000;
var angularSpeed = Math.abs(va) * 1000;
var threshold = props.inertiaThreshold;
return { dt: dt, vx: vx, vy: vy, va: va, linearSpeed: linearSpeed, angularSpeed: angularSpeed, threshold: threshold };
};
/** Handle RAF update */
SwipeInertia.prototype._handleRaf = function () {
var _a;
if (!this._raf) {
return;
}
var raf = this._raf;
var duration = this._raf.duration;
var _b = this.ctx, coords = _b.coords, props = _b.props;
var _c = this, velocity = _c._velocity, startCurrent = _c._saveCurrent, startRawMovement = _c._saveRawMovement, rawMovement = _c._rawMovement, distance = _c._modifiedDistance, initial = _c._initialVelocity;
var frameMs = duration;
// Delta
var dx = velocity.x * frameMs;
var dy = velocity.y * frameMs;
var dAngle = velocity.angle * frameMs;
// Friction
var frictionEase = raf.lerpFactor(props.inertiaDecay);
velocity.x = (0, utils_1.lerp)(velocity.x, 0, frictionEase);
velocity.y = (0, utils_1.lerp)(velocity.y, 0, frictionEase);
velocity.angle = (0, utils_1.lerp)(velocity.angle, 0, frictionEase);
// Movement
if (distance) {
var xP = this._getVelocityProgress(velocity.x, initial.x);
var yP = this._getVelocityProgress(velocity.y, initial.y);
var aP = this._getVelocityProgress(velocity.angle, initial.angle);
rawMovement.x = startRawMovement.x + distance.x * xP;
rawMovement.y = startRawMovement.y + distance.y * yP;
rawMovement.angle = startRawMovement.angle + distance.angle * aP;
}
else {
rawMovement.x += dx;
rawMovement.y += dy;
rawMovement.angle += dAngle;
}
// Bounce
var isBouncing = false;
var rawBounceEase = props.inertiaBounceEase;
var bounceEase = rawBounceEase >= 1 ? 1 : raf.lerpFactor(rawBounceEase);
// Bounce within bounds
var bounds = coords.bounds;
if (bounds === null || bounds === void 0 ? void 0 : bounds.x) {
var bx = this._applyAxisBounce('x', rawMovement.x, velocity.x, bounds.x, bounceEase);
rawMovement.x = bx.value;
velocity.x = bx.velocity;
isBouncing = 'bounceFinished' in bx ? true : isBouncing;
}
if (bounds === null || bounds === void 0 ? void 0 : bounds.y) {
var by = this._applyAxisBounce('y', rawMovement.y, velocity.y, bounds.y, bounceEase);
rawMovement.y = by.value;
velocity.y = by.velocity;
isBouncing = 'bounceFinished' in by ? true : isBouncing;
}
if (bounds === null || bounds === void 0 ? void 0 : bounds.angle) {
var ba = this._applyAxisBounce('angle', rawMovement.angle, velocity.angle, bounds.angle, bounceEase);
rawMovement.angle = ba.value;
velocity.angle = ba.velocity;
isBouncing = 'bounceFinished' in ba ? true : isBouncing;
}
// Callbacks
var totalX = rawMovement.x - startRawMovement.x;
var totalY = rawMovement.y - startRawMovement.y;
var totalA = rawMovement.angle - startRawMovement.angle;
var x = startCurrent.x + totalX;
var y = startCurrent.y + totalY;
var angle = startCurrent.angle + totalA;
(_a = this._onUpdate) === null || _a === void 0 ? void 0 : _a.call(this, { x: x, y: y, angle: angle });
// Stop
var linearStep = Math.hypot(dx, dy);
var angularStep = Math.abs(dAngle);
var shouldStop = linearStep < BELOW_THRESHOLD && angularStep < BELOW_THRESHOLD;
if (distance) {
shouldStop =
Math.abs(totalX - distance.x) < LERP_APPROX &&
Math.abs(totalY - distance.y) < LERP_APPROX &&
Math.abs(totalA - distance.angle) < LERP_APPROX;
}
if (!isBouncing && shouldStop) {
this.ctx.onEnd();
this._clear();
}
};
/** Calculate velocity progress */
SwipeInertia.prototype._getVelocityProgress = function (v, initial) {
if (Math.abs(initial) < BELOW_THRESHOLD) {
return 1;
}
var p = 1 - Math.abs(v / initial);
if (Math.abs(1 - p) < LERP_APPROX / 10) {
return 1;
}
return p;
};
SwipeInertia.prototype._predictDistance = function (velocity, decay, frameMs) {
if (frameMs === void 0) { frameMs = 1000 / 60; }
var k = (decay * 60) / 1000;
var r = Math.exp(-k * frameMs);
return (velocity * frameMs) / (1 - r);
};
/** Apply exponential axis bounce overflow */
SwipeInertia.prototype._applyAxisBounce = function (axis, value, velocity, bounds, ease) {
if (!bounds.length) {
return { value: value, velocity: velocity };
}
var snappy = this.ctx.coords.snap[axis];
var lo = typeof snappy === 'number' ? snappy : Math.min.apply(Math, bounds);
var hi = typeof snappy === 'number' ? snappy : Math.max.apply(Math, bounds);
if (value < lo || value > hi) {
var target = (0, utils_1.clamp)(value, lo, hi);
var val = (0, utils_1.lerp)(value, target, ease, LERP_APPROX);
var vel = (0, utils_1.lerp)(velocity, 0, ease, LERP_APPROX);
return {
value: val,
velocity: vel,
bounceFinished: val === target && vel === 0,
};
}
return { value: value, velocity: velocity };
};
/** Clear data and stop animation */
SwipeInertia.prototype._clear = function () {
var _a;
(_a = this._raf) === null || _a === void 0 ? void 0 : _a.destroy();
this._raf = undefined;
this._velocity = __assign({}, IDLE_STATE);
};
/** Stop inertia animation */
SwipeInertia.prototype.cancel = function () {
if (this._raf) {
this.ctx.onCancel();
}
this._clear();
};
/** Destroy instance */
SwipeInertia.prototype.destroy = function () {
this._clear();
};
return SwipeInertia;
}());
exports.SwipeInertia = SwipeInertia;
//# sourceMappingURL=index.js.map