VSEARCH / src /motion.js
bep40's picture
Deploy Vietnamese interior image field (1222 imgs, multilingual CLIP text tower)
c9e39f3 verified
Raw History Blame Contribute Delete
1.63 kB
/** Quintic paths retain velocity when interrupted; arc and acceleration settle to zero. */
export function sampleMotion(motion, now) {
const t = Math.max(0, Math.min(1, (now - motion.started) / motion.duration));
const t2 = t * t, t3 = t2 * t, t4 = t3 * t, t5 = t4 * t;
const ease = 10 * t3 - 15 * t4 + 6 * t5;
const carry = t - 6 * t3 + 8 * t4 - 3 * t5;
const arch = 64 * t3 * (1 - t) ** 3;
const de = 30 * t2 * (1 - t) ** 2;
const dc = 1 - 18 * t2 + 32 * t3 - 15 * t4;
const da = 192 * t2 * (1 - t) ** 2 * (1 - 2 * t);
const seconds = motion.duration / 1000;
const value = {}, velocity = {};
for (const key of ['x', 'y', 'scale']) {
const delta = motion.to[key] - motion.from[key];
const v = motion.velocity[key] || 0;
const bend = motion.arc[key] || 0;
value[key] = motion.from[key] + delta * ease + v * seconds * carry + bend * arch;
velocity[key] = delta * de / seconds + v * dc + bend * da / seconds;
}
return { value, velocity, done: t === 1 };
}
export function transition(node, target, now, immediate = false) {
const current = node.motion ? sampleMotion(node.motion, now) : { value: node, velocity: {} };
const from = { x: current.value.x, y: current.value.y, scale: current.value.scale };
const dx = target.x - from.x, dy = target.y - from.y;
const distance = Math.hypot(dx, dy);
const bend = Math.min(36, distance * 0.055);
node.motion = {
from, to: target, velocity: current.velocity,
arc: { x: -dy / (distance || 1) * bend, y: dx / (distance || 1) * bend },
started: now,
duration: immediate ? 1 : Math.min(950, 600 + distance * 0.22),
};
}