104 lines
4.0 KiB
JavaScript
104 lines
4.0 KiB
JavaScript
import { transformProps } from '../../render/html/utils/transform.mjs';
|
|
import { optimizedAppearDataAttribute } from '../optimized-appear/data-id.mjs';
|
|
import { animateMotionValue } from './motion-value.mjs';
|
|
import { isWillChangeMotionValue } from '../../value/use-will-change/is.mjs';
|
|
import { setTarget } from '../../render/utils/setters.mjs';
|
|
import { getValueTransition } from '../utils/transitions.mjs';
|
|
import { frame } from '../../frameloop/frame.mjs';
|
|
|
|
/**
|
|
* Decide whether we should block this animation. Previously, we achieved this
|
|
* just by checking whether the key was listed in protectedKeys, but this
|
|
* posed problems if an animation was triggered by afterChildren and protectedKeys
|
|
* had been set to true in the meantime.
|
|
*/
|
|
function shouldBlockAnimation({ protectedKeys, needsAnimating }, key) {
|
|
const shouldBlock = protectedKeys.hasOwnProperty(key) && needsAnimating[key] !== true;
|
|
needsAnimating[key] = false;
|
|
return shouldBlock;
|
|
}
|
|
function hasKeyframesChanged(value, target) {
|
|
const current = value.get();
|
|
if (Array.isArray(target)) {
|
|
for (let i = 0; i < target.length; i++) {
|
|
if (target[i] !== current)
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
return current !== target;
|
|
}
|
|
}
|
|
function animateTarget(visualElement, definition, { delay = 0, transitionOverride, type } = {}) {
|
|
let { transition = visualElement.getDefaultTransition(), transitionEnd, ...target } = visualElement.makeTargetAnimatable(definition);
|
|
const willChange = visualElement.getValue("willChange");
|
|
if (transitionOverride)
|
|
transition = transitionOverride;
|
|
const animations = [];
|
|
const animationTypeState = type &&
|
|
visualElement.animationState &&
|
|
visualElement.animationState.getState()[type];
|
|
for (const key in target) {
|
|
const value = visualElement.getValue(key);
|
|
const valueTarget = target[key];
|
|
if (!value ||
|
|
valueTarget === undefined ||
|
|
(animationTypeState &&
|
|
shouldBlockAnimation(animationTypeState, key))) {
|
|
continue;
|
|
}
|
|
const valueTransition = {
|
|
delay,
|
|
elapsed: 0,
|
|
...getValueTransition(transition || {}, key),
|
|
};
|
|
/**
|
|
* If this is the first time a value is being animated, check
|
|
* to see if we're handling off from an existing animation.
|
|
*/
|
|
if (window.HandoffAppearAnimations) {
|
|
const appearId = visualElement.getProps()[optimizedAppearDataAttribute];
|
|
if (appearId) {
|
|
const elapsed = window.HandoffAppearAnimations(appearId, key, value, frame);
|
|
if (elapsed !== null) {
|
|
valueTransition.elapsed = elapsed;
|
|
valueTransition.isHandoff = true;
|
|
}
|
|
}
|
|
}
|
|
let canSkip = !valueTransition.isHandoff &&
|
|
!hasKeyframesChanged(value, valueTarget);
|
|
if (valueTransition.type === "spring" &&
|
|
(value.getVelocity() || valueTransition.velocity)) {
|
|
canSkip = false;
|
|
}
|
|
/**
|
|
* Temporarily disable skipping animations if there's an animation in
|
|
* progress. Better would be to track the current target of a value
|
|
* and compare that against valueTarget.
|
|
*/
|
|
if (value.animation) {
|
|
canSkip = false;
|
|
}
|
|
if (canSkip)
|
|
continue;
|
|
value.start(animateMotionValue(key, value, valueTarget, visualElement.shouldReduceMotion && transformProps.has(key)
|
|
? { type: false }
|
|
: valueTransition));
|
|
const animation = value.animation;
|
|
if (isWillChangeMotionValue(willChange)) {
|
|
willChange.add(key);
|
|
animation.then(() => willChange.remove(key));
|
|
}
|
|
animations.push(animation);
|
|
}
|
|
if (transitionEnd) {
|
|
Promise.all(animations).then(() => {
|
|
transitionEnd && setTarget(visualElement, transitionEnd);
|
|
});
|
|
}
|
|
return animations;
|
|
}
|
|
|
|
export { animateTarget };
|