117 lines
5.2 KiB
JavaScript
117 lines
5.2 KiB
JavaScript
import { warning } from '../../utils/errors.mjs';
|
|
import { secondsToMilliseconds } from '../../utils/time-conversion.mjs';
|
|
import { instantAnimationState } from '../../utils/use-instant-transition-state.mjs';
|
|
import { createAcceleratedAnimation } from '../animators/waapi/create-accelerated-animation.mjs';
|
|
import { createInstantAnimation } from '../animators/instant.mjs';
|
|
import { getDefaultTransition } from '../utils/default-transitions.mjs';
|
|
import { isAnimatable } from '../utils/is-animatable.mjs';
|
|
import { getKeyframes } from '../utils/keyframes.mjs';
|
|
import { getValueTransition, isTransitionDefined } from '../utils/transitions.mjs';
|
|
import { animateValue } from '../animators/js/index.mjs';
|
|
import { MotionGlobalConfig } from '../../utils/GlobalConfig.mjs';
|
|
|
|
const animateMotionValue = (valueName, value, target, transition = {}) => {
|
|
return (onComplete) => {
|
|
const valueTransition = getValueTransition(transition, valueName) || {};
|
|
/**
|
|
* Most transition values are currently completely overwritten by value-specific
|
|
* transitions. In the future it'd be nicer to blend these transitions. But for now
|
|
* delay actually does inherit from the root transition if not value-specific.
|
|
*/
|
|
const delay = valueTransition.delay || transition.delay || 0;
|
|
/**
|
|
* Elapsed isn't a public transition option but can be passed through from
|
|
* optimized appear effects in milliseconds.
|
|
*/
|
|
let { elapsed = 0 } = transition;
|
|
elapsed = elapsed - secondsToMilliseconds(delay);
|
|
const keyframes = getKeyframes(value, valueName, target, valueTransition);
|
|
/**
|
|
* Check if we're able to animate between the start and end keyframes,
|
|
* and throw a warning if we're attempting to animate between one that's
|
|
* animatable and another that isn't.
|
|
*/
|
|
const originKeyframe = keyframes[0];
|
|
const targetKeyframe = keyframes[keyframes.length - 1];
|
|
const isOriginAnimatable = isAnimatable(valueName, originKeyframe);
|
|
const isTargetAnimatable = isAnimatable(valueName, targetKeyframe);
|
|
warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${valueName} from "${originKeyframe}" to "${targetKeyframe}". ${originKeyframe} is not an animatable value - to enable this animation set ${originKeyframe} to a value animatable to ${targetKeyframe} via the \`style\` property.`);
|
|
let options = {
|
|
keyframes,
|
|
velocity: value.getVelocity(),
|
|
ease: "easeOut",
|
|
...valueTransition,
|
|
delay: -elapsed,
|
|
onUpdate: (v) => {
|
|
value.set(v);
|
|
valueTransition.onUpdate && valueTransition.onUpdate(v);
|
|
},
|
|
onComplete: () => {
|
|
onComplete();
|
|
valueTransition.onComplete && valueTransition.onComplete();
|
|
},
|
|
};
|
|
/**
|
|
* If there's no transition defined for this value, we can generate
|
|
* unqiue transition settings for this value.
|
|
*/
|
|
if (!isTransitionDefined(valueTransition)) {
|
|
options = {
|
|
...options,
|
|
...getDefaultTransition(valueName, options),
|
|
};
|
|
}
|
|
/**
|
|
* Both WAAPI and our internal animation functions use durations
|
|
* as defined by milliseconds, while our external API defines them
|
|
* as seconds.
|
|
*/
|
|
if (options.duration) {
|
|
options.duration = secondsToMilliseconds(options.duration);
|
|
}
|
|
if (options.repeatDelay) {
|
|
options.repeatDelay = secondsToMilliseconds(options.repeatDelay);
|
|
}
|
|
if (!isOriginAnimatable ||
|
|
!isTargetAnimatable ||
|
|
instantAnimationState.current ||
|
|
valueTransition.type === false ||
|
|
MotionGlobalConfig.skipAnimations) {
|
|
/**
|
|
* If we can't animate this value, or the global instant animation flag is set,
|
|
* or this is simply defined as an instant transition, return an instant transition.
|
|
*/
|
|
return createInstantAnimation(instantAnimationState.current
|
|
? { ...options, delay: 0 }
|
|
: options);
|
|
}
|
|
/**
|
|
* Animate via WAAPI if possible.
|
|
*/
|
|
if (
|
|
/**
|
|
* If this is a handoff animation, the optimised animation will be running via
|
|
* WAAPI. Therefore, this animation must be JS to ensure it runs "under" the
|
|
* optimised animation.
|
|
*/
|
|
!transition.isHandoff &&
|
|
value.owner &&
|
|
value.owner.current instanceof HTMLElement &&
|
|
/**
|
|
* If we're outputting values to onUpdate then we can't use WAAPI as there's
|
|
* no way to read the value from WAAPI every frame.
|
|
*/
|
|
!value.owner.getProps().onUpdate) {
|
|
const acceleratedAnimation = createAcceleratedAnimation(value, valueName, options);
|
|
if (acceleratedAnimation)
|
|
return acceleratedAnimation;
|
|
}
|
|
/**
|
|
* If we didn't create an accelerated animation, create a JS animation
|
|
*/
|
|
return animateValue(options);
|
|
};
|
|
};
|
|
|
|
export { animateMotionValue };
|