vevet
Version:
Vevet is a JavaScript library for creative development that simplifies crafting rich interactions like split text animations, carousels, marquees, preloading, and more.
234 lines • 8.51 kB
JavaScript
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 { initVevet } from '../../global/initVevet';
import { cnHas } from '../../internal/cn';
import { doc } from '../../internal/env';
import { noopIfDestroyed } from '../../internal/noopIfDestroyed';
import { clamp, lerp } from '../../utils/math';
import { Preloader } from '../Preloader';
import { Raf } from '../Raf';
import { Timeline } from '../Timeline';
import { MUTABLE_PROPS, STATIC_PROPS } from './props';
import { preloadCustomElement } from './utils/preloadCustomElement';
import { preloadImage } from './utils/preloadImage';
import { preloadVideo } from './utils/preloadVideo';
export * from './types';
const PAGE_RESOURCE = `vevet-page-${Math.random()}`;
/**
* Page preloader for calculating and displaying the loading progress of resources (images, videos, custom elements).
* Provides smooth progress transitions.
*
* [Documentation](https://vevetjs.com/docs/ProgressPreloader)
*
* @group Components
*/
export class ProgressPreloader extends Preloader {
/**
* Retrieves the default static properties.
*/
_getStatic() {
return Object.assign(Object.assign({}, super._getStatic()), STATIC_PROPS);
}
/**
* Retrieves the default mutable properties.
*/
_getMutable() {
return Object.assign(Object.assign({}, super._getMutable()), MUTABLE_PROPS);
}
constructor(props, onCallbacks) {
super(props, onCallbacks);
/**
* List of custom resources to preload based on selectors.
*/
this._resources = [
{ id: PAGE_RESOURCE, weight: 1, loaded: 0 },
];
/**
* Current interpolated progress value for smooth transitions.
*/
this._progress = 0;
// Initialize animation frame if interpolation is enabled
this._raf = new Raf({ enabled: true });
this._raf.on('frame', () => this._handleUpdate());
// Start preloading resources
this._fetchImages();
this._fetchVideos();
this._fetchResources();
// Handle resources on page load
initVevet().onLoad(() => this.resolveResource(PAGE_RESOURCE));
}
/** Container source for preloader resources. */
get resourceContainer() {
var _a;
return (_a = this.props.resourceContainer) !== null && _a !== void 0 ? _a : doc;
}
/**
* The list of custom resources to preload.
*/
get resources() {
return this._resources;
}
/**
* Calculates the total number of resources to preload, including their weight.
*/
get totalWeight() {
return this.resources.reduce((acc, { weight }) => acc + weight, 0);
}
/**
* Loaded weight
*/
get loadedWeight() {
return this.resources.reduce((acc, { loaded }) => acc + loaded, 0);
}
/**
* Current loading progress (0 to 1).
*/
get loadProgress() {
return this.loadedWeight / this.totalWeight;
}
/**
* Gets the current progress value.
*/
get progress() {
return this._progress;
}
/**
* Linear interpolation factor
*/
get lerpEase() {
return clamp(Math.abs(this.props.lerp));
}
/** Preload images */
_fetchImages() {
if (!this.props.preloadImages) {
return;
}
let list = Array.from(this.resourceContainer.querySelectorAll('img'));
list = list.filter((resource) => {
const isIgnored = cnHas(resource, this.props.ignoreClassName);
return !isIgnored && resource.loading !== 'lazy';
});
this._resources.push(...list.map((resource) => ({
id: resource,
weight: 1,
loaded: 0,
})));
list.forEach((element) => {
preloadImage(element, () => this.resolveResource(element));
});
}
/** Preload videos */
_fetchVideos() {
if (!this.props.preloadVideos) {
return;
}
let list = Array.from(this.resourceContainer.querySelectorAll('video'));
list = list.filter((resource) => !cnHas(resource, this.props.ignoreClassName));
this._resources.push(...list.map((resource) => ({
id: resource,
weight: 1,
loaded: 0,
})));
list.forEach((element) => {
preloadVideo(element, () => this.resolveResource(element));
});
}
/** Preload custom resources */
_fetchResources() {
let list = Array.from(this.resourceContainer.querySelectorAll(this.props.customSelector));
list = list.filter((resource) => !cnHas(resource, this.props.ignoreClassName));
list.forEach((element) => {
let weight = parseInt(element.getAttribute('data-weight') || '1', 10);
weight = Number.isNaN(weight) ? 1 : clamp(weight, 1, Infinity);
const resource = {
id: element,
weight,
loaded: 0,
};
this._resources.push(resource);
preloadCustomElement(resource, (loadedWeight) => this.resolveResource(element, loadedWeight));
});
}
/**
* Adds a custom resource
* @param id - The custom resource element or identifier to preload.
* @param weight - The resource weight
*/
addResource(id, weight = 1) {
const hasResource = this.resources.some((item) => item.id === id);
if (hasResource) {
throw new Error('Resource already exists');
}
this._resources.push({ id, weight, loaded: 0 });
}
/**
* Emits a resource load event and updates the count of loaded resources.
* @param id - The resource element or identifier being loaded.
*/
resolveResource(id, loadedWeight) {
const resource = this.resources.find((item) => item.id === id);
if (!resource) {
return;
}
const targetWeight = loadedWeight !== null && loadedWeight !== void 0 ? loadedWeight : resource.weight;
resource.loaded = clamp(targetWeight, 0, resource.weight);
this.callbacks.emit('resource', resource);
}
/**
* Handles updates to the preloader's progress, triggering events and animations as needed.
* @param newProgress - The updated progress value.
*/
_handleUpdate() {
var _a;
const ease = this._raf.lerpFactor(this.lerpEase);
const newProgress = lerp(this._progress, this.loadProgress, ease);
this._progress = newProgress;
this.callbacks.emit('progress', undefined);
if (this.loadProgress < 1) {
return;
}
(_a = this._raf) === null || _a === void 0 ? void 0 : _a.destroy();
const startProgress = this.progress;
if (startProgress >= 1) {
return;
}
const endTimeline = new Timeline({ duration: this.props.endDuration });
this.onDestroy(() => endTimeline.destroy());
endTimeline.on('update', ({ progress }) => {
const diff = 1 - startProgress;
this._progress = startProgress + diff * progress;
this.callbacks.emit('progress', undefined);
});
endTimeline.play();
}
/**
* Resolves when the page and all resources are fully loaded.
*/
_onLoaded(callback) {
let isFinish = false;
this.callbacks.on('progress', (() => {
if (this.progress >= 1 && !isFinish) {
isFinish = true;
callback();
}
}), { protected: true, name: this.name });
}
/**
* Cleans up resources and destroys the preloader instance.
*/
_destroy() {
super._destroy();
this._raf.destroy();
}
}
__decorate([
noopIfDestroyed
], ProgressPreloader.prototype, "addResource", null);
__decorate([
noopIfDestroyed
], ProgressPreloader.prototype, "resolveResource", null);
//# sourceMappingURL=index.js.map