| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- import { time } from 'motion-dom';
- import { flushKeyframeResolvers } from '../../render/utils/KeyframesResolver.mjs';
- import { instantAnimationState } from '../../utils/use-instant-transition-state.mjs';
- import { canAnimate } from './utils/can-animate.mjs';
- import { getFinalKeyframe } from './waapi/utils/get-final-keyframe.mjs';
- /**
- * Maximum time allowed between an animation being created and it being
- * resolved for us to use the latter as the start time.
- *
- * This is to ensure that while we prefer to "start" an animation as soon
- * as it's triggered, we also want to avoid a visual jump if there's a big delay
- * between these two moments.
- */
- const MAX_RESOLVE_DELAY = 40;
- class BaseAnimation {
- constructor({ autoplay = true, delay = 0, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", ...options }) {
- // Track whether the animation has been stopped. Stopped animations won't restart.
- this.isStopped = false;
- this.hasAttemptedResolve = false;
- this.createdAt = time.now();
- this.options = {
- autoplay,
- delay,
- type,
- repeat,
- repeatDelay,
- repeatType,
- ...options,
- };
- this.updateFinishedPromise();
- }
- /**
- * This method uses the createdAt and resolvedAt to calculate the
- * animation startTime. *Ideally*, we would use the createdAt time as t=0
- * as the following frame would then be the first frame of the animation in
- * progress, which would feel snappier.
- *
- * However, if there's a delay (main thread work) between the creation of
- * the animation and the first commited frame, we prefer to use resolvedAt
- * to avoid a sudden jump into the animation.
- */
- calcStartTime() {
- if (!this.resolvedAt)
- return this.createdAt;
- return this.resolvedAt - this.createdAt > MAX_RESOLVE_DELAY
- ? this.resolvedAt
- : this.createdAt;
- }
- /**
- * A getter for resolved data. If keyframes are not yet resolved, accessing
- * this.resolved will synchronously flush all pending keyframe resolvers.
- * This is a deoptimisation, but at its worst still batches read/writes.
- */
- get resolved() {
- if (!this._resolved && !this.hasAttemptedResolve) {
- flushKeyframeResolvers();
- }
- return this._resolved;
- }
- /**
- * A method to be called when the keyframes resolver completes. This method
- * will check if its possible to run the animation and, if not, skip it.
- * Otherwise, it will call initPlayback on the implementing class.
- */
- onKeyframesResolved(keyframes, finalKeyframe) {
- this.resolvedAt = time.now();
- this.hasAttemptedResolve = true;
- const { name, type, velocity, delay, onComplete, onUpdate, isGenerator, } = this.options;
- /**
- * If we can't animate this value with the resolved keyframes
- * then we should complete it immediately.
- */
- if (!isGenerator && !canAnimate(keyframes, name, type, velocity)) {
- // Finish immediately
- if (instantAnimationState.current || !delay) {
- onUpdate &&
- onUpdate(getFinalKeyframe(keyframes, this.options, finalKeyframe));
- onComplete && onComplete();
- this.resolveFinishedPromise();
- return;
- }
- // Finish after a delay
- else {
- this.options.duration = 0;
- }
- }
- const resolvedAnimation = this.initPlayback(keyframes, finalKeyframe);
- if (resolvedAnimation === false)
- return;
- this._resolved = {
- keyframes,
- finalKeyframe,
- ...resolvedAnimation,
- };
- this.onPostResolved();
- }
- onPostResolved() { }
- /**
- * Allows the returned animation to be awaited or promise-chained. Currently
- * resolves when the animation finishes at all but in a future update could/should
- * reject if its cancels.
- */
- then(resolve, reject) {
- return this.currentFinishedPromise.then(resolve, reject);
- }
- flatten() {
- if (!this.options.allowFlatten)
- return;
- this.options.type = "keyframes";
- this.options.ease = "linear";
- }
- updateFinishedPromise() {
- this.currentFinishedPromise = new Promise((resolve) => {
- this.resolveFinishedPromise = resolve;
- });
- }
- }
- export { BaseAnimation };
|