vevet
Version:
Vevet is a JavaScript library for creative development that simplifies crafting rich interactions like split text animations, carousels, marquees, preloading, and more.
180 lines (138 loc) • 4.09 kB
text/typescript
import { clamp, inRange, lerp, scoped } from '@/utils/math';
import { Snap } from '..';
import { toPixels } from '@/utils';
export class SnapTrack {
constructor(protected snap: Snap) {}
/** The current track value */
protected _current = 0;
/** The target track value */
protected _target = 0;
/** Gets the current track value. */
get current() {
return this._current;
}
/** Sets the current track value */
set current(value: number) {
this._current = value;
}
/** Gets the target track value. */
get target() {
return this._target;
}
/** Sets the target track value */
set target(value: number) {
this._target = value;
}
/** Set a value to current & target value instantly */
public set(value: number) {
this.current = value;
this.target = value;
}
/** If can loop */
get canLoop() {
return this.snap.props.loop && this.snap.slides.length > 1;
}
/** Interpolate the current track value */
public lerp(factor: number) {
// todo: move to renderer?
let { target } = this;
const { snap, min, max } = this;
// Edge space & resistance
if (!snap.props.loop) {
const { domSize, props } = snap;
const edgeSpace = (1 - props.edgeFriction) * domSize;
if (target < min) {
const edgeProgress = 1 - scoped(target, -domSize, min);
target = min - edgeProgress * edgeSpace;
} else if (target > max) {
const edgeProgress = scoped(target, max, max + domSize);
target = max + edgeProgress * edgeSpace;
}
target = clamp(target, min - edgeSpace, max + edgeSpace);
}
this.current = lerp(this.current, target, factor, 0.000001);
}
/** Whether the track is interpolated */
get isInterpolated() {
return this.current === this.target;
}
/** Get minimum track value */
get min() {
const { snap } = this;
if (this.canLoop || snap.isEmpty) {
return 0;
}
if (snap.props.centered) {
const firstSlide = snap.slides[0];
if (firstSlide.size > snap.domSize) {
return snap.domSize / 2 - firstSlide.size / 2;
}
}
return 0;
}
/** Get maximum track value */
get max() {
const { domSize, slides, isEmpty, props } = this.snap;
const { canLoop } = this;
if (isEmpty) {
return 0;
}
const firstSlide = slides[0];
const lastSlide = slides[slides.length - 1];
const lastCoordWithSlide = lastSlide.staticCoord + lastSlide.size;
let max = canLoop
? lastCoordWithSlide + toPixels(props.gap)
: lastCoordWithSlide - domSize;
if (canLoop) {
return max;
}
if (props.centered) {
max += domSize / 2 - firstSlide.size / 2;
if (lastSlide.size < domSize) {
max += domSize / 2 - lastSlide.size / 2;
}
}
if (!props.centered) {
max = Math.max(max, 0);
}
return max;
}
/** Get track progress. From 0 to 1 if not loop. From -Infinity to Infinity if loop */
get progress() {
return this.current / this.max;
}
/** Iterate track target value */
public iterateTarget(delta: number) {
const { snap } = this;
this.target += delta;
// @ts-ignore
// eslint-disable-next-line no-underscore-dangle
snap._raf.play();
}
/** Clamp target value between min and max values */
public clampTarget() {
const { snap } = this;
if (!this.canLoop) {
this.target = clamp(this.target, this.min, this.max);
}
// @ts-ignore
// eslint-disable-next-line no-underscore-dangle
snap._raf.play();
}
/** If the start has been reached */
get isStart() {
return Math.round(this.target) <= this.min;
}
/** If the end has been reached */
get isEnd() {
return Math.round(this.target) >= this.max;
}
/** Check if the active slide is larger than the container and is being scrolled */
get isSlideScrolling() {
const { snap } = this;
const { domSize } = snap;
return snap.slides.some(
({ size, coord }) => size > domSize && inRange(coord, domSize - size, 0),
);
}
}