Initial commit
This commit is contained in:
391
node_modules/framer-motion/dist/es/animation/animators/MainThreadAnimation.mjs
generated
vendored
Normal file
391
node_modules/framer-motion/dist/es/animation/animators/MainThreadAnimation.mjs
generated
vendored
Normal file
@ -0,0 +1,391 @@
|
||||
import { KeyframeResolver } from '../../render/utils/KeyframesResolver.mjs';
|
||||
import { spring } from '../generators/spring/index.mjs';
|
||||
import { inertia } from '../generators/inertia.mjs';
|
||||
import { keyframes } from '../generators/keyframes.mjs';
|
||||
import { BaseAnimation } from './BaseAnimation.mjs';
|
||||
import { pipe } from '../../utils/pipe.mjs';
|
||||
import { mix } from '../../utils/mix/index.mjs';
|
||||
import { calcGeneratorDuration } from '../generators/utils/calc-duration.mjs';
|
||||
import { millisecondsToSeconds, secondsToMilliseconds } from '../../utils/time-conversion.mjs';
|
||||
import { clamp } from '../../utils/clamp.mjs';
|
||||
import { invariant } from 'motion-utils';
|
||||
import { frameloopDriver } from './drivers/driver-frameloop.mjs';
|
||||
import { getFinalKeyframe } from './waapi/utils/get-final-keyframe.mjs';
|
||||
import { isGenerator } from '../generators/utils/is-generator.mjs';
|
||||
|
||||
const generators = {
|
||||
decay: inertia,
|
||||
inertia,
|
||||
tween: keyframes,
|
||||
keyframes: keyframes,
|
||||
spring,
|
||||
};
|
||||
const percentToProgress = (percent) => percent / 100;
|
||||
/**
|
||||
* Animation that runs on the main thread. Designed to be WAAPI-spec in the subset of
|
||||
* features we expose publically. Mostly the compatibility is to ensure visual identity
|
||||
* between both WAAPI and main thread animations.
|
||||
*/
|
||||
class MainThreadAnimation extends BaseAnimation {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
/**
|
||||
* The time at which the animation was paused.
|
||||
*/
|
||||
this.holdTime = null;
|
||||
/**
|
||||
* The time at which the animation was cancelled.
|
||||
*/
|
||||
this.cancelTime = null;
|
||||
/**
|
||||
* The current time of the animation.
|
||||
*/
|
||||
this.currentTime = 0;
|
||||
/**
|
||||
* Playback speed as a factor. 0 would be stopped, -1 reverse and 2 double speed.
|
||||
*/
|
||||
this.playbackSpeed = 1;
|
||||
/**
|
||||
* The state of the animation to apply when the animation is resolved. This
|
||||
* allows calls to the public API to control the animation before it is resolved,
|
||||
* without us having to resolve it first.
|
||||
*/
|
||||
this.pendingPlayState = "running";
|
||||
/**
|
||||
* The time at which the animation was started.
|
||||
*/
|
||||
this.startTime = null;
|
||||
this.state = "idle";
|
||||
/**
|
||||
* This method is bound to the instance to fix a pattern where
|
||||
* animation.stop is returned as a reference from a useEffect.
|
||||
*/
|
||||
this.stop = () => {
|
||||
this.resolver.cancel();
|
||||
this.isStopped = true;
|
||||
if (this.state === "idle")
|
||||
return;
|
||||
this.teardown();
|
||||
const { onStop } = this.options;
|
||||
onStop && onStop();
|
||||
};
|
||||
const { name, motionValue, element, keyframes } = this.options;
|
||||
const KeyframeResolver$1 = (element === null || element === void 0 ? void 0 : element.KeyframeResolver) || KeyframeResolver;
|
||||
const onResolved = (resolvedKeyframes, finalKeyframe) => this.onKeyframesResolved(resolvedKeyframes, finalKeyframe);
|
||||
this.resolver = new KeyframeResolver$1(keyframes, onResolved, name, motionValue, element);
|
||||
this.resolver.scheduleResolve();
|
||||
}
|
||||
flatten() {
|
||||
super.flatten();
|
||||
// If we've already resolved the animation, re-initialise it
|
||||
if (this._resolved) {
|
||||
Object.assign(this._resolved, this.initPlayback(this._resolved.keyframes));
|
||||
}
|
||||
}
|
||||
initPlayback(keyframes$1) {
|
||||
const { type = "keyframes", repeat = 0, repeatDelay = 0, repeatType, velocity = 0, } = this.options;
|
||||
const generatorFactory = isGenerator(type)
|
||||
? type
|
||||
: generators[type] || keyframes;
|
||||
/**
|
||||
* If our generator doesn't support mixing numbers, we need to replace keyframes with
|
||||
* [0, 100] and then make a function that maps that to the actual keyframes.
|
||||
*
|
||||
* 100 is chosen instead of 1 as it works nicer with spring animations.
|
||||
*/
|
||||
let mapPercentToKeyframes;
|
||||
let mirroredGenerator;
|
||||
if (generatorFactory !== keyframes &&
|
||||
typeof keyframes$1[0] !== "number") {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
invariant(keyframes$1.length === 2, `Only two keyframes currently supported with spring and inertia animations. Trying to animate ${keyframes$1}`);
|
||||
}
|
||||
mapPercentToKeyframes = pipe(percentToProgress, mix(keyframes$1[0], keyframes$1[1]));
|
||||
keyframes$1 = [0, 100];
|
||||
}
|
||||
const generator = generatorFactory({ ...this.options, keyframes: keyframes$1 });
|
||||
/**
|
||||
* If we have a mirror repeat type we need to create a second generator that outputs the
|
||||
* mirrored (not reversed) animation and later ping pong between the two generators.
|
||||
*/
|
||||
if (repeatType === "mirror") {
|
||||
mirroredGenerator = generatorFactory({
|
||||
...this.options,
|
||||
keyframes: [...keyframes$1].reverse(),
|
||||
velocity: -velocity,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* If duration is undefined and we have repeat options,
|
||||
* we need to calculate a duration from the generator.
|
||||
*
|
||||
* We set it to the generator itself to cache the duration.
|
||||
* Any timeline resolver will need to have already precalculated
|
||||
* the duration by this step.
|
||||
*/
|
||||
if (generator.calculatedDuration === null) {
|
||||
generator.calculatedDuration = calcGeneratorDuration(generator);
|
||||
}
|
||||
const { calculatedDuration } = generator;
|
||||
const resolvedDuration = calculatedDuration + repeatDelay;
|
||||
const totalDuration = resolvedDuration * (repeat + 1) - repeatDelay;
|
||||
return {
|
||||
generator,
|
||||
mirroredGenerator,
|
||||
mapPercentToKeyframes,
|
||||
calculatedDuration,
|
||||
resolvedDuration,
|
||||
totalDuration,
|
||||
};
|
||||
}
|
||||
onPostResolved() {
|
||||
const { autoplay = true } = this.options;
|
||||
this.play();
|
||||
if (this.pendingPlayState === "paused" || !autoplay) {
|
||||
this.pause();
|
||||
}
|
||||
else {
|
||||
this.state = this.pendingPlayState;
|
||||
}
|
||||
}
|
||||
tick(timestamp, sample = false) {
|
||||
const { resolved } = this;
|
||||
// If the animations has failed to resolve, return the final keyframe.
|
||||
if (!resolved) {
|
||||
const { keyframes } = this.options;
|
||||
return { done: true, value: keyframes[keyframes.length - 1] };
|
||||
}
|
||||
const { finalKeyframe, generator, mirroredGenerator, mapPercentToKeyframes, keyframes, calculatedDuration, totalDuration, resolvedDuration, } = resolved;
|
||||
if (this.startTime === null)
|
||||
return generator.next(0);
|
||||
const { delay, repeat, repeatType, repeatDelay, onUpdate } = this.options;
|
||||
/**
|
||||
* requestAnimationFrame timestamps can come through as lower than
|
||||
* the startTime as set by performance.now(). Here we prevent this,
|
||||
* though in the future it could be possible to make setting startTime
|
||||
* a pending operation that gets resolved here.
|
||||
*/
|
||||
if (this.speed > 0) {
|
||||
this.startTime = Math.min(this.startTime, timestamp);
|
||||
}
|
||||
else if (this.speed < 0) {
|
||||
this.startTime = Math.min(timestamp - totalDuration / this.speed, this.startTime);
|
||||
}
|
||||
// Update currentTime
|
||||
if (sample) {
|
||||
this.currentTime = timestamp;
|
||||
}
|
||||
else if (this.holdTime !== null) {
|
||||
this.currentTime = this.holdTime;
|
||||
}
|
||||
else {
|
||||
// Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 =
|
||||
// 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for
|
||||
// example.
|
||||
this.currentTime =
|
||||
Math.round(timestamp - this.startTime) * this.speed;
|
||||
}
|
||||
// Rebase on delay
|
||||
const timeWithoutDelay = this.currentTime - delay * (this.speed >= 0 ? 1 : -1);
|
||||
const isInDelayPhase = this.speed >= 0
|
||||
? timeWithoutDelay < 0
|
||||
: timeWithoutDelay > totalDuration;
|
||||
this.currentTime = Math.max(timeWithoutDelay, 0);
|
||||
// If this animation has finished, set the current time to the total duration.
|
||||
if (this.state === "finished" && this.holdTime === null) {
|
||||
this.currentTime = totalDuration;
|
||||
}
|
||||
let elapsed = this.currentTime;
|
||||
let frameGenerator = generator;
|
||||
if (repeat) {
|
||||
/**
|
||||
* Get the current progress (0-1) of the animation. If t is >
|
||||
* than duration we'll get values like 2.5 (midway through the
|
||||
* third iteration)
|
||||
*/
|
||||
const progress = Math.min(this.currentTime, totalDuration) / resolvedDuration;
|
||||
/**
|
||||
* Get the current iteration (0 indexed). For instance the floor of
|
||||
* 2.5 is 2.
|
||||
*/
|
||||
let currentIteration = Math.floor(progress);
|
||||
/**
|
||||
* Get the current progress of the iteration by taking the remainder
|
||||
* so 2.5 is 0.5 through iteration 2
|
||||
*/
|
||||
let iterationProgress = progress % 1.0;
|
||||
/**
|
||||
* If iteration progress is 1 we count that as the end
|
||||
* of the previous iteration.
|
||||
*/
|
||||
if (!iterationProgress && progress >= 1) {
|
||||
iterationProgress = 1;
|
||||
}
|
||||
iterationProgress === 1 && currentIteration--;
|
||||
currentIteration = Math.min(currentIteration, repeat + 1);
|
||||
/**
|
||||
* Reverse progress if we're not running in "normal" direction
|
||||
*/
|
||||
const isOddIteration = Boolean(currentIteration % 2);
|
||||
if (isOddIteration) {
|
||||
if (repeatType === "reverse") {
|
||||
iterationProgress = 1 - iterationProgress;
|
||||
if (repeatDelay) {
|
||||
iterationProgress -= repeatDelay / resolvedDuration;
|
||||
}
|
||||
}
|
||||
else if (repeatType === "mirror") {
|
||||
frameGenerator = mirroredGenerator;
|
||||
}
|
||||
}
|
||||
elapsed = clamp(0, 1, iterationProgress) * resolvedDuration;
|
||||
}
|
||||
/**
|
||||
* If we're in negative time, set state as the initial keyframe.
|
||||
* This prevents delay: x, duration: 0 animations from finishing
|
||||
* instantly.
|
||||
*/
|
||||
const state = isInDelayPhase
|
||||
? { done: false, value: keyframes[0] }
|
||||
: frameGenerator.next(elapsed);
|
||||
if (mapPercentToKeyframes) {
|
||||
state.value = mapPercentToKeyframes(state.value);
|
||||
}
|
||||
let { done } = state;
|
||||
if (!isInDelayPhase && calculatedDuration !== null) {
|
||||
done =
|
||||
this.speed >= 0
|
||||
? this.currentTime >= totalDuration
|
||||
: this.currentTime <= 0;
|
||||
}
|
||||
const isAnimationFinished = this.holdTime === null &&
|
||||
(this.state === "finished" || (this.state === "running" && done));
|
||||
if (isAnimationFinished && finalKeyframe !== undefined) {
|
||||
state.value = getFinalKeyframe(keyframes, this.options, finalKeyframe);
|
||||
}
|
||||
if (onUpdate) {
|
||||
onUpdate(state.value);
|
||||
}
|
||||
if (isAnimationFinished) {
|
||||
this.finish();
|
||||
}
|
||||
return state;
|
||||
}
|
||||
get duration() {
|
||||
const { resolved } = this;
|
||||
return resolved ? millisecondsToSeconds(resolved.calculatedDuration) : 0;
|
||||
}
|
||||
get time() {
|
||||
return millisecondsToSeconds(this.currentTime);
|
||||
}
|
||||
set time(newTime) {
|
||||
newTime = secondsToMilliseconds(newTime);
|
||||
this.currentTime = newTime;
|
||||
if (this.holdTime !== null || this.speed === 0) {
|
||||
this.holdTime = newTime;
|
||||
}
|
||||
else if (this.driver) {
|
||||
this.startTime = this.driver.now() - newTime / this.speed;
|
||||
}
|
||||
}
|
||||
get speed() {
|
||||
return this.playbackSpeed;
|
||||
}
|
||||
set speed(newSpeed) {
|
||||
const hasChanged = this.playbackSpeed !== newSpeed;
|
||||
this.playbackSpeed = newSpeed;
|
||||
if (hasChanged) {
|
||||
this.time = millisecondsToSeconds(this.currentTime);
|
||||
}
|
||||
}
|
||||
play() {
|
||||
if (!this.resolver.isScheduled) {
|
||||
this.resolver.resume();
|
||||
}
|
||||
if (!this._resolved) {
|
||||
this.pendingPlayState = "running";
|
||||
return;
|
||||
}
|
||||
if (this.isStopped)
|
||||
return;
|
||||
const { driver = frameloopDriver, onPlay, startTime } = this.options;
|
||||
if (!this.driver) {
|
||||
this.driver = driver((timestamp) => this.tick(timestamp));
|
||||
}
|
||||
onPlay && onPlay();
|
||||
const now = this.driver.now();
|
||||
if (this.holdTime !== null) {
|
||||
this.startTime = now - this.holdTime;
|
||||
}
|
||||
else if (!this.startTime) {
|
||||
this.startTime = startTime !== null && startTime !== void 0 ? startTime : this.calcStartTime();
|
||||
}
|
||||
else if (this.state === "finished") {
|
||||
this.startTime = now;
|
||||
}
|
||||
if (this.state === "finished") {
|
||||
this.updateFinishedPromise();
|
||||
}
|
||||
this.cancelTime = this.startTime;
|
||||
this.holdTime = null;
|
||||
/**
|
||||
* Set playState to running only after we've used it in
|
||||
* the previous logic.
|
||||
*/
|
||||
this.state = "running";
|
||||
this.driver.start();
|
||||
}
|
||||
pause() {
|
||||
var _a;
|
||||
if (!this._resolved) {
|
||||
this.pendingPlayState = "paused";
|
||||
return;
|
||||
}
|
||||
this.state = "paused";
|
||||
this.holdTime = (_a = this.currentTime) !== null && _a !== void 0 ? _a : 0;
|
||||
}
|
||||
complete() {
|
||||
if (this.state !== "running") {
|
||||
this.play();
|
||||
}
|
||||
this.pendingPlayState = this.state = "finished";
|
||||
this.holdTime = null;
|
||||
}
|
||||
finish() {
|
||||
this.teardown();
|
||||
this.state = "finished";
|
||||
const { onComplete } = this.options;
|
||||
onComplete && onComplete();
|
||||
}
|
||||
cancel() {
|
||||
if (this.cancelTime !== null) {
|
||||
this.tick(this.cancelTime);
|
||||
}
|
||||
this.teardown();
|
||||
this.updateFinishedPromise();
|
||||
}
|
||||
teardown() {
|
||||
this.state = "idle";
|
||||
this.stopDriver();
|
||||
this.resolveFinishedPromise();
|
||||
this.updateFinishedPromise();
|
||||
this.startTime = this.cancelTime = null;
|
||||
this.resolver.cancel();
|
||||
}
|
||||
stopDriver() {
|
||||
if (!this.driver)
|
||||
return;
|
||||
this.driver.stop();
|
||||
this.driver = undefined;
|
||||
}
|
||||
sample(time) {
|
||||
this.startTime = 0;
|
||||
return this.tick(time, true);
|
||||
}
|
||||
}
|
||||
// Legacy interface
|
||||
function animateValue(options) {
|
||||
return new MainThreadAnimation(options);
|
||||
}
|
||||
|
||||
export { MainThreadAnimation, animateValue };
|
||||
Reference in New Issue
Block a user