kinetic-slider
Version:
A WebGL-powered kinetic slider component using PIXI.js
299 lines (297 loc) • 10.6 kB
JavaScript
/**
* Slide initialization state
*/
var SlideState;
(function (SlideState) {
/** Slide is not initialized at all */
SlideState["UNINITIALIZED"] = "uninitialized";
/** Slide has a lightweight placeholder */
SlideState["PLACEHOLDER"] = "placeholder";
/** Slide is fully loaded and ready for display */
SlideState["LOADED"] = "loaded";
/** Slide is currently visible */
SlideState["ACTIVE"] = "active";
/** Slide failed to load */
SlideState["ERROR"] = "error";
})(SlideState || (SlideState = {}));
/**
* Manages a sliding window of initialized slides to optimize memory usage
* and improve performance by only fully loading slides that are visible
* or likely to become visible soon.
*/
class SlidingWindowManager {
/** Current active slide index */
activeIndex;
/** Size of the window on each side of the active slide */
windowSize;
/** Total number of slides */
totalSlides;
/** Information about each slide */
slides;
/** Whether debug logging is enabled */
debug;
/** Resource manager for tracking resources */
resourceManager;
/** Direction of the last navigation (-1 for prev, 1 for next, 0 for initial) */
lastDirection = 0;
/**
* Creates a new SlidingWindowManager
*
* @param options - Configuration options
* @param resourceManager - Optional ResourceManager for resource tracking
*/
constructor(options, resourceManager) {
this.windowSize = options.windowSize ?? 2;
this.totalSlides = options.totalSlides;
this.activeIndex = options.initialIndex ?? 0;
this.debug = options.debug ?? false;
this.resourceManager = resourceManager;
// Initialize slide info array
this.slides = Array(this.totalSlides).fill(null).map((_, index) => ({
index,
state: SlideState.UNINITIALIZED,
inWindow: this.isInWindow(index, this.activeIndex)
}));
this.log(`Initialized with ${this.totalSlides} slides, window size ±${this.windowSize}, active index ${this.activeIndex}`);
this.log(`Initial window: ${this.getWindowIndices().join(', ')}`);
}
/**
* Log a message if debug is enabled
*/
log(message, level = 'info') {
if (!this.debug)
return;
const prefix = `[SlidingWindowManager]`;
switch (level) {
case 'info':
console.log(`${prefix} ${message}`);
break;
case 'warn':
console.warn(`${prefix} ${message}`);
break;
case 'error':
console.error(`${prefix} ${message}`);
break;
}
}
/**
* Check if a slide index is within the current window
*
* @param index - Slide index to check
* @param centerIndex - Center of the window (usually activeIndex)
* @returns Whether the index is within the window
*/
isInWindow(index, centerIndex) {
const distance = Math.abs(index - centerIndex);
return distance <= this.windowSize;
}
/**
* Get all slide indices that should be in the current window
*
* @returns Array of slide indices in the window
*/
getWindowIndices() {
const indices = [];
// Include the active index and windowSize slides on each side
for (let i = Math.max(0, this.activeIndex - this.windowSize); i <= Math.min(this.totalSlides - 1, this.activeIndex + this.windowSize); i++) {
indices.push(i);
}
return indices;
}
/**
* Get all slide indices that should be in the extended window
* (includes prediction based on navigation direction)
*
* @returns Array of slide indices in the extended window
*/
getExtendedWindowIndices() {
const indices = this.getWindowIndices();
// Add extra slides in the direction of navigation
if (this.lastDirection !== 0) {
const extraIndex = this.lastDirection > 0
? this.activeIndex + this.windowSize + 1
: this.activeIndex - this.windowSize - 1;
if (extraIndex >= 0 && extraIndex < this.totalSlides) {
indices.push(extraIndex);
}
}
return indices;
}
/**
* Update the active slide index and recalculate the window
*
* @param newIndex - New active slide index
* @returns Object containing arrays of indices that entered and left the window
*/
updateActiveIndex(newIndex) {
if (newIndex < 0 || newIndex >= this.totalSlides) {
this.log(`Invalid index: ${newIndex}`, 'error');
return { entered: [], left: [] };
}
// Calculate direction of navigation
this.lastDirection = newIndex > this.activeIndex ? 1 : -1;
const oldWindowIndices = this.getWindowIndices();
const oldActiveIndex = this.activeIndex;
// Update active index
this.activeIndex = newIndex;
// Update active slide info
if (this.slides[newIndex]) {
this.slides[newIndex].state = SlideState.ACTIVE;
this.slides[newIndex].lastActiveTime = Date.now();
}
// Update previous active slide
if (this.slides[oldActiveIndex] && oldActiveIndex !== newIndex) {
if (this.slides[oldActiveIndex].state === SlideState.ACTIVE) {
this.slides[oldActiveIndex].state = SlideState.LOADED;
}
}
// Get new window indices
const newWindowIndices = this.getWindowIndices();
// Calculate which indices entered and left the window
const entered = newWindowIndices.filter(index => !oldWindowIndices.includes(index));
const left = oldWindowIndices.filter(index => !newWindowIndices.includes(index));
// Update inWindow flag for all slides
this.slides.forEach(slide => {
slide.inWindow = this.isInWindow(slide.index, this.activeIndex);
});
this.log(`Updated active index to ${newIndex}, direction: ${this.lastDirection}`);
this.log(`Window changed: +[${entered.join(', ')}] -[${left.join(', ')}]`);
return { entered, left };
}
/**
* Register a sprite for a slide
*
* @param index - Slide index
* @param sprite - Sprite instance
* @param state - Current state of the slide
*/
registerSlide(index, sprite, state = SlideState.LOADED) {
if (index < 0 || index >= this.totalSlides) {
this.log(`Invalid index for registerSlide: ${index}`, 'error');
return;
}
this.slides[index] = {
...this.slides[index],
sprite,
state: index === this.activeIndex ? SlideState.ACTIVE : state,
inWindow: this.isInWindow(index, this.activeIndex)
};
this.log(`Registered slide ${index} with state ${state}`);
}
/**
* Update the state of a slide
*
* @param index - Slide index
* @param state - New state
*/
updateSlideState(index, state) {
if (index < 0 || index >= this.totalSlides) {
this.log(`Invalid index for updateSlideState: ${index}`, 'error');
return;
}
this.slides[index].state = state;
this.log(`Updated slide ${index} state to ${state}`);
}
/**
* Get information about a slide
*
* @param index - Slide index
* @returns Slide information or null if index is invalid
*/
getSlideInfo(index) {
if (index < 0 || index >= this.totalSlides) {
this.log(`Invalid index for getSlideInfo: ${index}`, 'error');
return null;
}
return this.slides[index];
}
/**
* Get the current active index
*
* @returns Active slide index
*/
getActiveIndex() {
return this.activeIndex;
}
/**
* Get all slide information
*
* @returns Array of slide information
*/
getAllSlides() {
return [...this.slides];
}
/**
* Get slides that need to be initialized (converted from UNINITIALIZED to at least PLACEHOLDER)
*
* @returns Array of slide indices that need initialization
*/
getSlidesToInitialize() {
const windowIndices = this.getExtendedWindowIndices();
return windowIndices.filter(index => this.slides[index].state === SlideState.UNINITIALIZED);
}
/**
* Get slides that need to be fully loaded (converted from PLACEHOLDER to LOADED)
*
* @returns Array of slide indices that need to be fully loaded
*/
getSlidesToLoad() {
const windowIndices = this.getWindowIndices();
return windowIndices.filter(index => this.slides[index].state === SlideState.PLACEHOLDER);
}
/**
* Get slides that can be unloaded (converted from LOADED to PLACEHOLDER)
*
* @returns Array of slide indices that can be unloaded
*/
getSlidesToUnload() {
return this.slides
.filter(slide => !slide.inWindow &&
(slide.state === SlideState.LOADED || slide.state === SlideState.ACTIVE))
.map(slide => slide.index);
}
/**
* Set the window size
*
* @param size - New window size
*/
setWindowSize(size) {
if (size < 1) {
this.log(`Invalid window size: ${size}, must be at least 1`, 'error');
return;
}
this.windowSize = size;
// Recalculate which slides are in the window
this.slides.forEach(slide => {
slide.inWindow = this.isInWindow(slide.index, this.activeIndex);
});
this.log(`Window size updated to ±${size}`);
}
/**
* Get the current window size
*
* @returns Current window size
*/
getWindowSize() {
return this.windowSize;
}
/**
* Get the last navigation direction
*
* @returns Last direction (-1 for prev, 1 for next, 0 for initial)
*/
getLastDirection() {
return this.lastDirection;
}
/**
* Alias for updateActiveIndex to maintain naming consistency
*
* @param newIndex - New current slide index
* @returns Object containing arrays of indices that entered and left the window
*/
updateCurrentIndex(newIndex) {
return this.updateActiveIndex(newIndex);
}
}
export { SlideState, SlidingWindowManager as default };
//# sourceMappingURL=SlidingWindowManager.js.map