animare
Version:
Advanced animation library for modern JavaScript.
15 lines • 7.49 kB
JavaScript
import{Direction,Timing}from'./types.js';import{isAlternateDirection,isReverseDirection,validateAnimationValues}from'./utils/helpers.js';import{clamp,normalizePercentage}from'./utils/utils.js';export default class Animation{/** The index of the animation. */#index;/** The start time of the animation, without accounting for delays, in milliseconds. */#start;/** The end time of the animation, without accounting for delays and repeats, in milliseconds. */#end;/** The starting point in time, in milliseconds. */#startPoint;/** The end point in time, including delays and repeats, in milliseconds. */ /** The starting value of the animation, relative to its direction. */#startValue;/** The current animated value. */#value;/** The ending value of the animation, relative to its direction. */#endValue;/** The number of remaining times to apply the delay. */#delayCount;/** The current lap of the alternate direction. */#alternateLap=0;/** The number of remaining times the animation should play. */#playCount;/** The current animation progress, from `0` to `1`, excluding delays. */#progress=0;/** The overall progress, from `0` to `1`, including delays and repeats. */#overallProgress=0;/** The elapsed time of the animation, in milliseconds. */#elapsedTime=0;/** Indicates whether the animation has finished. */#isFinished=false;/** Indicates whether the animation is currently playing. */#isPlaying=false;/** Indicates whether the animation is in reverse. */#isReverse;/** Indicates whether the animation alternates direction. */#isAlternate;/** The reference to the prepared animation values. */#previousTimelineRef;#isProgressAt(progress){let tolerance=arguments.length>1&&arguments[1]!==undefined?arguments[1]:0.001;return Math.abs(this.#progress-progress)<tolerance;}#isTimeAt(time){let tolerance=arguments.length>1&&arguments[1]!==undefined?arguments[1]:5;return Math.abs(this.#elapsedTime-time)<tolerance;}// preserve info object reference
#infoRef=Object.create(null);get info(){return Object.assign(this.#infoRef,{name:this.animationRef.name,index:this.#index,value:this.#value,progress:this.#progress,overallProgress:this.#overallProgress,elapsedTime:this.#elapsedTime,isFinished:this.#isFinished,delayCount:this.#delayCount,playCount:this.#playCount,isPlaying:this.#isPlaying,isProgressAt:this.#isProgressAt.bind(this),isTimeAt:this.#isTimeAt.bind(this)});}constructor(animation,previousTimeline,index){this.#index=index;this.animationRef=animation;this.#previousTimelineRef=previousTimeline;this.Setup();}Setup(){this.#isFinished=false;this.#isPlaying=false;this.#elapsedTime=0;this.#progress=0;this.#overallProgress=0;this.#alternateLap=0;this.#playCount=0;this.#delayCount=0;this.#isAlternate=isAlternateDirection(this.animationRef.direction);this.#isReverse=isReverseDirection(this.animationRef.direction);this.#startValue=this.#isReverse?this.animationRef.to:this.animationRef.from;this.#value=this.#isReverse?this.animationRef.to:this.animationRef.from;this.#endValue=this.#isReverse?this.animationRef.from:this.animationRef.to;const offset=this.animationRef.offset;const delay=this.animationRef.delayCount===0?0:this.animationRef.delay;const overallDuration=this.animationRef.duration*this.animationRef.playCount+delay*this.animationRef.delayCount;// ensure that the first animation is always `Timing.FromStart`
const timing=this.#index===0?Timing.FromStart:this.animationRef.timing;switch(timing){case Timing.FromStart:this.#startPoint=offset;break;case Timing.AfterPrevious:if(!this.#previousTimelineRef)throw new Error('The previous animation is not defined.');this.#startPoint=this.#previousTimelineRef.endPoint+offset;break;case Timing.WithPrevious:if(!this.#previousTimelineRef)throw new Error('The previous animation is not defined.');this.#startPoint=this.#previousTimelineRef.#startPoint+offset;break;}this.endPoint=this.#startPoint+overallDuration;this.#start=this.#startPoint+delay;this.#end=this.#start+this.animationRef.duration;}Update(elapsedTime){// technically disabled
if(this.animationRef.playCount===0)return;// the current time is after this animation (finished)
if(elapsedTime>=this.endPoint){this.#isPlaying=false;this.#isFinished=true;this.#playCount=this.animationRef.playCount;this.#delayCount=this.animationRef.delayCount;this.#progress=1;this.#overallProgress=1;this.#elapsedTime=this.endPoint-this.#startPoint;const isReverse=this.animationRef.direction===Direction.Reverse||this.animationRef.direction===Direction.Alternate;this.#value=isReverse?this.animationRef.from:this.animationRef.to;return;}// the current time is before this animation (not started yet)
if(elapsedTime<this.#startPoint){this.#isPlaying=false;this.#isFinished=false;this.#playCount=0;this.#delayCount=0;this.#progress=0;this.#overallProgress=0;this.#elapsedTime=0;this.#value=this.#isReverse?this.animationRef.to:this.animationRef.from;return;}// the current time is in this animation (playing)
this.#isPlaying=true;this.#overallProgress=normalizePercentage((elapsedTime-this.#startPoint)/(this.endPoint-this.#startPoint));this.#calculateValues(elapsedTime);}/**
* Set or update the animation values.
*
* ⚠️ **Warning** ⚠️ This method will throw an error if the animation values are invalid.
*/Set(animation){Object.assign(this.animationRef,animation);validateAnimationValues(this.animationRef);}#calculateValues(elapsedTime){const withDelayCount=this.animationRef.delayCount;const withoutDelayLength=this.animationRef.duration;const withoutDelayTotalLength=withoutDelayLength*(this.animationRef.playCount-withDelayCount);const withDelayLength=this.animationRef.duration+this.animationRef.delay;const withDelayTotalLength=withDelayLength*withDelayCount+this.animationRef.delay*withDelayCount;const totalLength=withDelayTotalLength+withoutDelayTotalLength;const targetLength=totalLength*this.#overallProgress;// falls under with delay parts
if(withDelayTotalLength&&targetLength<=withDelayTotalLength){const at=clamp(Math.floor(targetLength/withDelayLength),0,withDelayCount-1);const delay=withDelayCount===0?0:this.animationRef.delay;this.#start=this.#startPoint+withDelayLength*at+delay;this.#delayCount=at+1;this.#playCount=at+1;// falls under without delay parts
}else{const remainingLength=targetLength-withDelayTotalLength;const at=withDelayCount+clamp(Math.floor(remainingLength/withoutDelayLength),0,this.animationRef.playCount-1);this.#start=this.#startPoint+withDelayTotalLength+withoutDelayLength*(at-withDelayCount);this.#delayCount=withDelayCount;this.#playCount=at+1;}this.#end=this.#start+this.animationRef.duration;this.#elapsedTime=elapsedTime-this.#start;this.#progress=normalizePercentage(this.#elapsedTime/(this.#end-this.#start));const internalProgress=this.#calculateProgress();this.#value=this.#startValue+(this.#endValue-this.#startValue)*this.animationRef.ease(internalProgress);}/** - Calculate the internal progress relative to the direction. */#calculateProgress(){if(!this.#isAlternate)return this.#progress;const progress=(this.#progress<=0.5?this.#progress:this.#progress-0.5)*2;this.#alternateLap=this.#progress<=0.5?0:1;// first lap
if(this.#alternateLap===0){this.#startValue=this.#isReverse?this.animationRef.to:this.animationRef.from;this.#endValue=this.#isReverse?this.animationRef.from:this.animationRef.to;return progress;}// second lap
this.#startValue=this.#isReverse?this.animationRef.from:this.animationRef.to;this.#endValue=this.#isReverse?this.animationRef.to:this.animationRef.from;return progress;}}