UNPKG

vevet

Version:

Vevet is a JavaScript library for creative development that simplifies crafting rich interactions like split text animations, carousels, marquees, preloading, and more.

177 lines 6.04 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { Module } from '../../base/Module'; import { noopIfDestroyed } from '../../internal/noopIfDestroyed'; import { MUTABLE_PROPS, STATIC_PROPS } from './props'; export * from './types'; /** * Manages an animation frame loop with configurable FPS and playback controls. * * [Documentation](https://vevetjs.com/docs/Raf) * * @group Components */ export class Raf extends Module { /** Get default static properties */ _getStatic() { return Object.assign(Object.assign({}, super._getStatic()), STATIC_PROPS); } /** Get default mutable properties */ _getMutable() { return Object.assign(Object.assign({}, super._getMutable()), MUTABLE_PROPS); } constructor(props, onCallbacks) { super(props, onCallbacks); /** Indicates if the animation frame is currently running */ this._isPlaying = false; /** Active requestAnimationFrame ID, or `null` if not running */ this._raf = null; /** Timestamp of the last frame */ this._lastTimestamp = null; /** Timestamp of the current frame */ this._timestamp = null; /** Current frame index */ this._index = 0; /** Real-time FPS */ this._fps = 60; /** Duration of the last frame in ms */ this._duration = 0; // Initialize FPS this._fps = this.props.fps === 'auto' ? this._fps : this.props.fps; // Play on init if (this.props.enabled) { this._play(); } } /** Playback state of the animation frame */ get isPlaying() { return this._isPlaying; } /** Timestamp of the current frame */ get timestamp() { var _a; return (_a = this._timestamp) !== null && _a !== void 0 ? _a : 0; } /** Current frame index */ get index() { return this._index; } /** Real-time FPS */ get fps() { return this._fps; } /** Duration of the last frame in ms */ get duration() { return this._duration; } /** Scaling coefficient based on a 60 FPS target */ get fpsFactor() { return 60 / this.fps; } /** Handle property mutations */ _handleProps(props) { super._handleProps(props); this._lastTimestamp = null; if (this.props.enabled) { this._play(); } else { this._pause(); } } /** Start the animation loop */ play() { if (this.props.enabled) { return; } this.updateProps({ enabled: true }); } /** Internal method to start the loop */ _play() { if (this.isPlaying) { return; } this._isPlaying = true; this.callbacks.emit('play', undefined); this.callbacks.emit('toggle', undefined); this._raf = window.requestAnimationFrame(this._animate.bind(this)); } /** Pause the animation loop */ pause() { if (!this.props.enabled) { return; } this.updateProps({ enabled: false }); } /** Internal method to pause the loop */ _pause() { if (!this.isPlaying) { return; } if (this._raf) { window.cancelAnimationFrame(this._raf); this._raf = null; } this._isPlaying = false; this.callbacks.emit('pause', undefined); this.callbacks.emit('toggle', undefined); } /** Animation loop handler, calculates FPS, and triggers callbacks */ _animate() { var _a, _b; if (!this._isPlaying) { return; } this._raf = window.requestAnimationFrame(this._animate.bind(this)); const minFrameDuration = this.props.fps === 'auto' ? 1 : 1000 / this.props.fps; this._timestamp = performance.now(); (_a = this._lastTimestamp) !== null && _a !== void 0 ? _a : (this._lastTimestamp = this._timestamp); const duration = this._timestamp - ((_b = this._lastTimestamp) !== null && _b !== void 0 ? _b : this._timestamp); if (duration < minFrameDuration) { return; } this._duration = duration; this._lastTimestamp = this._timestamp; this._index += 1; this._computeFPS(); this.callbacks.emit('frame', { fps: this.fps, fpsFactor: this.fpsFactor, duration: this.duration, lerpFactor: this.lerpFactor.bind(this), }); } /** Calculate linear interpolation factor to make animations run the same regardless of FPS */ lerpFactor(ease) { return 1 - Math.exp(-ease * 60 * (this.duration / 1000)); } /** Compute real-time FPS from frame durations */ _computeFPS() { const { duration, index, props } = this; if ((index > 10 && index % props.fpsRecalcFrames !== 0) || duration <= 0 || duration > 250) { return; } const standardFps = 60; const standardFrameTime = 1000 / standardFps; const fpsMultiplier = standardFrameTime / duration; this._fps = Math.round(60 * fpsMultiplier) || 1; } /** Destroy the animation frame and stop the loop */ _destroy() { this.pause(); super._destroy(); } } __decorate([ noopIfDestroyed ], Raf.prototype, "play", null); __decorate([ noopIfDestroyed ], Raf.prototype, "pause", null); //# sourceMappingURL=index.js.map