103 lines
3.3 KiB
JavaScript
103 lines
3.3 KiB
JavaScript
import { motionValue } from './index.mjs';
|
|
import { JSAnimation } from '../animation/JSAnimation.mjs';
|
|
import { isMotionValue } from './utils/is-motion-value.mjs';
|
|
import { frame } from '../frameloop/frame.mjs';
|
|
|
|
/**
|
|
* Create a `MotionValue` that animates to its latest value using any transition type.
|
|
* Can either be a value or track another `MotionValue`.
|
|
*
|
|
* ```jsx
|
|
* const x = motionValue(0)
|
|
* const y = followValue(x, { type: "spring", stiffness: 300 })
|
|
* // or with tween
|
|
* const z = followValue(x, { type: "tween", duration: 0.5, ease: "easeOut" })
|
|
* ```
|
|
*
|
|
* @param source - Initial value or MotionValue to track
|
|
* @param options - Animation transition options
|
|
* @returns `MotionValue`
|
|
*
|
|
* @public
|
|
*/
|
|
function followValue(source, options) {
|
|
const initialValue = isMotionValue(source) ? source.get() : source;
|
|
const value = motionValue(initialValue);
|
|
attachFollow(value, source, options);
|
|
return value;
|
|
}
|
|
/**
|
|
* Attach an animation to a MotionValue that will animate whenever the value changes.
|
|
* Similar to attachSpring but supports any transition type (spring, tween, inertia, etc.)
|
|
*
|
|
* @param value - The MotionValue to animate
|
|
* @param source - Initial value or MotionValue to track
|
|
* @param options - Animation transition options
|
|
* @returns Cleanup function
|
|
*
|
|
* @public
|
|
*/
|
|
function attachFollow(value, source, options = {}) {
|
|
const initialValue = value.get();
|
|
let activeAnimation = null;
|
|
let latestValue = initialValue;
|
|
let latestSetter;
|
|
const unit = typeof initialValue === "string"
|
|
? initialValue.replace(/[\d.-]/g, "")
|
|
: undefined;
|
|
const stopAnimation = () => {
|
|
if (activeAnimation) {
|
|
activeAnimation.stop();
|
|
activeAnimation = null;
|
|
}
|
|
};
|
|
const startAnimation = () => {
|
|
stopAnimation();
|
|
const currentValue = asNumber(value.get());
|
|
const targetValue = asNumber(latestValue);
|
|
// Don't animate if we're already at the target
|
|
if (currentValue === targetValue) {
|
|
return;
|
|
}
|
|
activeAnimation = new JSAnimation({
|
|
keyframes: [currentValue, targetValue],
|
|
velocity: value.getVelocity(),
|
|
// Default to spring if no type specified (matches useSpring behavior)
|
|
type: "spring",
|
|
restDelta: 0.001,
|
|
restSpeed: 0.01,
|
|
...options,
|
|
onUpdate: latestSetter,
|
|
});
|
|
};
|
|
value.attach((v, set) => {
|
|
latestValue = v;
|
|
latestSetter = (latest) => set(parseValue(latest, unit));
|
|
frame.postRender(() => {
|
|
startAnimation();
|
|
value["events"].animationStart?.notify();
|
|
activeAnimation?.then(() => {
|
|
value["events"].animationComplete?.notify();
|
|
});
|
|
});
|
|
}, stopAnimation);
|
|
if (isMotionValue(source)) {
|
|
const removeSourceOnChange = source.on("change", (v) => value.set(parseValue(v, unit)));
|
|
const removeValueOnDestroy = value.on("destroy", removeSourceOnChange);
|
|
return () => {
|
|
removeSourceOnChange();
|
|
removeValueOnDestroy();
|
|
};
|
|
}
|
|
return stopAnimation;
|
|
}
|
|
function parseValue(v, unit) {
|
|
return unit ? v + unit : v;
|
|
}
|
|
function asNumber(v) {
|
|
return typeof v === "number" ? v : parseFloat(v);
|
|
}
|
|
|
|
export { attachFollow, followValue };
|
|
//# sourceMappingURL=follow-value.mjs.map
|