motion-dom
Version:
128 lines (125 loc) • 4.96 kB
JavaScript
import { invariant, removeItem } from 'motion-utils';
import { positionalKeys } from '../../render/utils/keys-position.mjs';
import { motionValue } from '../../value/index.mjs';
import { animateMotionValue } from '../interfaces/motion-value.mjs';
import { frame } from '../../frameloop/frame.mjs';
/**
* Effects registered via `animate.addEffect()`, most recent first.
*/
const effects = [];
function addEffect(effect) {
process.env.NODE_ENV !== "production" &&
invariant(typeof effect.test === "function" &&
typeof effect.read === "function", "Effects passed to animate.addEffect() need test() and read().", "effect-missing-test");
removeEffect(effect);
effects.unshift(effect);
}
function removeEffect(effect) {
removeItem(effects, effect);
}
function findEffect(subject) {
return effects.find((effect) => effect.test(subject));
}
/**
* Animate each key in `keyframes` on the motion value `getValue` returns
* for it. `element` is what the keyframe resolver uses to read, render
* and measure the subject when the effect defers resolution to it.
*/
function animateValues(getValue, keyframes, transition = {}, element) {
const animations = [];
const { velocity, path } = transition;
const reduceMotion = transition.reduceMotion ?? element?.shouldReduceMotion;
/**
* Only elements have x/y to move along a path. path claims them by
* deleting them, so hand it a copy.
*/
if (path?.animateValues && element) {
keyframes = { ...keyframes };
path.animateValues(getValue, keyframes, transition, 0, animations);
}
for (const key in keyframes) {
if (key === "transition" || key === "transitionEnd")
continue;
const target = keyframes[key];
if (target === undefined)
continue;
const value = getValue(key);
/**
* If the value is already at the defined target, skip the animation.
* We still re-assert the value via frame.update to take precedence
* over any stale transitionEnd callbacks from previous animations.
*/
const current = value.get();
if (current !== undefined &&
!value.isAnimating() &&
!Array.isArray(target) &&
target === current &&
!velocity) {
frame.update(() => value.set(target));
continue;
}
value.run(animateMotionValue(key, value, target, reduceMotion && positionalKeys.has(key)
? { type: false }
: transition, element));
value.animation &&
animations.push(value.animation);
}
const { transitionEnd } = keyframes;
if (transitionEnd) {
const applyTransitionEnd = () => frame.update(() => {
for (const key in transitionEnd) {
getValue(key).set(transitionEnd[key]);
}
});
animations.length
? Promise.all(animations).then(applyTransitionEnd)
: applyTransitionEnd();
}
return animations;
}
/**
* Animate the keys of `subject` via `effect`. Motion values are created on
* first animation and bound to the subject through the effect for the
* rest of its life.
*
* Without `element`, a new value is seeded synchronously from the first
* keyframe or `effect.read()`. With it, the value starts undefined and the
* keyframe resolver reads the subject (batched, with unit conversion by
* measurement) via `element`.
*/
function animateEffectSubject(effect, subject, keyframes, transition, element) {
/**
* Values made for this call are bound with one effect call, after the
* loop, rather than one each.
*/
let created;
let isBound = false;
const animations = animateValues((key) => {
let value = created?.[key] ?? effect.get(subject, key);
if (!value) {
let initial;
if (!element) {
const target = keyframes[key];
initial =
firstKeyframe(target) ??
effect.read(subject, key, target);
process.env.NODE_ENV !== "production" &&
invariant(initial !== undefined, `"${key}" can't be read from the animated subject. Provide [from, to] keyframes.`, "effect-unreadable-value");
}
value = motionValue(initial, { owner: element });
isBound
? effect(subject, { [key]: value })
: ((created || (created = {}))[key] = value);
}
return value;
}, keyframes, transition, element);
isBound = true;
created && effect(subject, created);
return animations;
}
function firstKeyframe(target) {
const first = Array.isArray(target) ? target[0] : undefined;
return first === null ? undefined : first;
}
export { addEffect, animateEffectSubject, animateValues, findEffect, removeEffect };
//# sourceMappingURL=effects.mjs.map