kinetic-slider
Version:
A WebGL-powered kinetic slider component using PIXI.js
842 lines (839 loc) • 39.9 kB
JavaScript
import { useState, useRef, useCallback, useEffect } from 'react';
import { Container, Sprite, Assets } from 'pixi.js';
import { calculateSpriteScale } from '../utils/calculateSpriteScale.js';
import { gsap } from 'gsap';
import { AnimationCoordinator, AnimationGroupType } from '../managers/AnimationCoordinator.js';
// Development environment check
const isDevelopment = "production" === 'development';
/**
* Hook to create and manage slide sprites with atlas support
*/
const useSlides = ({ sliderRef, pixi, props, resourceManager, atlasManager, onSlideChange, slidingWindowManager }) => {
// Debug logging of props
console.log("useSlides received useSlidesAtlas:", props.useSlidesAtlas);
console.log("useSlides received props:", props);
// Track loading state
const [isLoading, setIsLoading] = useState(false);
const [loadingProgress, setLoadingProgress] = useState(0);
// Ref to store active transitions
const activeTransitionRef = useRef(null);
// Get the animation coordinator
const animationCoordinator = AnimationCoordinator.getInstance();
// Get the slidesBasePath from props, defaulting to '/images/' if not provided
props.slidesBasePath || '/images/';
// Normalize path for atlas frame lookup
const normalizePath = (imagePath) => {
// For paths that start with a slash, remove it for atlas lookup
if (imagePath.startsWith('/')) {
return imagePath.substring(1);
}
return imagePath;
};
// Helper to check if useSlidesAtlas is enabled (handling different possible values)
const isUseSlidesAtlasEnabled = () => {
// Handle all possible representations of "true"
if (props.useSlidesAtlas === true)
return true;
if (typeof props.useSlidesAtlas === 'string' && props.useSlidesAtlas === 'true')
return true;
// Handle numeric representations (needs type checking)
if (typeof props.useSlidesAtlas === 'number' && props.useSlidesAtlas === 1)
return true;
if (typeof props.useSlidesAtlas === 'string' && props.useSlidesAtlas === '1')
return true;
// Default to false for all other cases
return false;
};
// Check if assets are available in atlas
const areAssetsInAtlas = useCallback(() => {
// First check if atlasManager and slidesAtlas are available
if (!atlasManager || !props.slidesAtlas) {
return false;
}
// Check if useSlidesAtlas is enabled
const useSlidesAtlasEnabled = isUseSlidesAtlasEnabled();
if (!useSlidesAtlasEnabled) {
return false;
}
const result = props.images.every(imagePath => {
const normalizedPath = normalizePath(imagePath);
const atlasId = atlasManager.hasFrame(normalizedPath);
return !!atlasId;
});
return result;
}, [atlasManager, props.images, props.slidesAtlas, props.useSlidesAtlas]);
// Effect to create slides from atlas or individual images
useEffect(() => {
if (!pixi.app.current || !pixi.app.current.stage) {
return;
}
// Check if we have images to display
if (!props.images.length) {
return;
}
// Check if slider ref is available for dimensions
if (!sliderRef.current) {
return;
}
// Create a dedicated container for slides if it doesn't exist
let slidesContainer;
try {
const app = pixi.app.current;
if (app.stage.children.length > 0 && app.stage.children[0] instanceof Container) {
slidesContainer = app.stage.children[0];
}
else {
slidesContainer = new Container();
slidesContainer.label = 'slidesContainer';
app.stage.addChild(slidesContainer);
// Track container with resource manager if available
if (resourceManager) {
resourceManager.trackDisplayObject(slidesContainer);
}
}
// Clear existing slides with proper cleanup
pixi.slides.current.forEach(sprite => {
if (sprite && sprite.parent) {
try {
sprite.parent.removeChild(sprite);
}
catch (error) {
if (isDevelopment) ;
}
}
});
pixi.slides.current = [];
// Enhanced handling of the useAtlas decision
const useSlidesAtlasEnabled = isUseSlidesAtlasEnabled();
const useAtlas = atlasManager && props.slidesAtlas && areAssetsInAtlas() && useSlidesAtlasEnabled;
if (isDevelopment) ;
if (useAtlas) {
loadSlidesFromAtlas(slidesContainer);
}
else {
loadSlidesFromIndividualImages(slidesContainer);
}
}
catch (error) {
setIsLoading(false);
}
}, [pixi.app.current, props.images, resourceManager, sliderRef, atlasManager, props.slidesAtlas, props.useSlidesAtlas]);
/**
* Load slides from texture atlas
*/
const loadSlidesFromAtlas = async (slidesContainer) => {
if (!pixi.app.current || !sliderRef.current || !atlasManager)
return;
try {
setIsLoading(true);
setLoadingProgress(0);
if (isDevelopment) ;
const app = pixi.app.current;
const sliderWidth = sliderRef.current.clientWidth;
const sliderHeight = sliderRef.current.clientHeight;
// Prepare for loading from atlas
const totalImages = props.images.length;
let loadedCount = 0;
// Get visibility window indices if sliding window manager is available
const visibilityWindowIndices = slidingWindowManager
? slidingWindowManager.getWindowIndices()
: [];
if (isDevelopment && slidingWindowManager) ;
// Create sprites for each image using the atlas
for (const [index, imagePath] of props.images.entries()) {
try {
// Check if this slide is in the visibility window
const isInVisibilityWindow = !slidingWindowManager ||
visibilityWindowIndices.includes(index);
if (isDevelopment && slidingWindowManager) ;
if (isInVisibilityWindow) {
// Fully load the slide if it's within the visibility window
// Normalize path for atlas lookup
const normalizedPath = normalizePath(imagePath);
if (isDevelopment) ;
// Get texture from atlas
const texture = atlasManager.getFrameTexture(normalizedPath, props.slidesAtlas);
if (!texture) {
throw new Error(`Frame ${normalizedPath} not found in atlas ${props.slidesAtlas}`);
}
// Track texture with resource manager if available
if (resourceManager) {
resourceManager.trackTexture(imagePath, texture);
}
// Create the sprite with the texture from atlas
const sprite = new Sprite(texture);
sprite.anchor.set(0.5);
sprite.x = app.screen.width / 2;
sprite.y = app.screen.height / 2;
// Set initial state - only show the first slide
sprite.alpha = index === 0 ? 1 : 0;
sprite.visible = index === 0;
// Calculate and apply scale
try {
const { scale, baseScale } = calculateSpriteScale(texture.width, texture.height, sliderWidth, sliderHeight);
sprite.scale.set(scale);
sprite.baseScale = baseScale;
}
catch (scaleError) {
if (isDevelopment) ;
// Fallback scaling
sprite.scale.set(1);
sprite.baseScale = 1;
}
// Mark as being in the visibility window
sprite._inVisibilityWindow = true;
// Track the sprite with resource manager if available
if (resourceManager) {
resourceManager.trackDisplayObject(sprite);
}
// Add to container and store reference
slidesContainer.addChild(sprite);
pixi.slides.current.push(sprite);
if (isDevelopment) ;
}
else {
// Create a placeholder for slides outside the visibility window
const placeholderOptions = {
width: sliderWidth,
height: sliderHeight,
color: 0x333333,
showIndex: isDevelopment, // Show index in development mode
index,
trackWithResourceManager: true,
resourceManager,
renderer: app.renderer
};
// Import the createPlaceholderSprite function dynamically to avoid circular dependencies
const { createPlaceholderSprite } = await import('../utils/placeholderUtils.js');
// Create the placeholder sprite
const placeholderSprite = createPlaceholderSprite(placeholderOptions);
// Position the placeholder at the center
placeholderSprite.x = app.screen.width / 2;
placeholderSprite.y = app.screen.height / 2;
// Set initial state - only show the first slide
placeholderSprite.alpha = index === 0 ? 1 : 0;
placeholderSprite.visible = index === 0;
// Set scale similar to real sprites
placeholderSprite.scale.set(1);
placeholderSprite.baseScale = 1;
// Mark as placeholder and outside visibility window
placeholderSprite._isPlaceholder = true;
placeholderSprite._placeholderIndex = index;
placeholderSprite._inVisibilityWindow = false;
// Add to container and store reference
slidesContainer.addChild(placeholderSprite);
pixi.slides.current.push(placeholderSprite);
if (isDevelopment) ;
}
// Update progress
loadedCount++;
const progress = (loadedCount / totalImages) * 100;
setLoadingProgress(progress);
}
catch (error) {
if (isDevelopment) ;
// Fallback to individual image loading if atlas frame not found
const texture = await Assets.load(imagePath);
createSlideFromTexture(texture, imagePath, index, slidesContainer, app, sliderWidth, sliderHeight);
// Update progress
loadedCount++;
setLoadingProgress((loadedCount / totalImages) * 100);
}
}
setIsLoading(false);
setLoadingProgress(100);
if (isDevelopment) ;
}
catch (error) {
// Fallback to individual image loading
loadSlidesFromIndividualImages(slidesContainer);
}
};
/**
* Load slides from individual images (fallback method)
*/
const loadSlidesFromIndividualImages = async (slidesContainer) => {
if (!pixi.app.current || !sliderRef.current)
return;
try {
setIsLoading(true);
setLoadingProgress(0);
if (isDevelopment) ;
// Prepare the list of images to load
const app = pixi.app.current;
const sliderWidth = sliderRef.current.clientWidth;
const sliderHeight = sliderRef.current.clientHeight;
if (isDevelopment) ;
// Get visibility window indices if sliding window manager is available
const visibilityWindowIndices = slidingWindowManager
? slidingWindowManager.getWindowIndices()
: [];
if (isDevelopment && slidingWindowManager) ;
// Filter out images that are not in the visibility window
const imagesToLoad = props.images
.filter((_, index) => !slidingWindowManager || visibilityWindowIndices.includes(index))
.filter(image => !Assets.cache.has(image));
if (isDevelopment) ;
// Add assets to a bundle for batch loading and progress tracking
if (imagesToLoad.length > 0) {
// Create an assets bundle
Assets.addBundle('slider-images', imagesToLoad.reduce((acc, image, index) => {
acc[`slide-${index}`] = image;
return acc;
}, {}));
// Load the bundle with progress tracking
await Assets.loadBundle('slider-images', (progress) => {
setLoadingProgress(progress * 100);
});
}
// Create sprites for each image
for (const [index, image] of props.images.entries()) {
try {
// Check if this slide is in the visibility window
const isInVisibilityWindow = !slidingWindowManager ||
visibilityWindowIndices.includes(index);
if (isDevelopment && slidingWindowManager) ;
if (isInVisibilityWindow) {
// Fully load the slide if it's within the visibility window
// Get texture from cache
const texture = Assets.get(image);
// Create slide sprite
const sprite = createSlideFromTexture(texture, image, index, slidesContainer, app, sliderWidth, sliderHeight);
// Mark as being in the visibility window
if (sprite) {
sprite._inVisibilityWindow = true;
}
if (isDevelopment) ;
}
else {
// Create a placeholder for slides outside the visibility window
const placeholderOptions = {
width: sliderWidth,
height: sliderHeight,
color: 0x333333,
showIndex: isDevelopment, // Show index in development mode
index,
trackWithResourceManager: true,
resourceManager,
renderer: app.renderer
};
// Import the createPlaceholderSprite function dynamically to avoid circular dependencies
const { createPlaceholderSprite } = await import('../utils/placeholderUtils.js');
// Create the placeholder sprite
const placeholderSprite = createPlaceholderSprite(placeholderOptions);
// Position the placeholder at the center
placeholderSprite.x = app.screen.width / 2;
placeholderSprite.y = app.screen.height / 2;
// Set initial state - only show the first slide
placeholderSprite.alpha = index === 0 ? 1 : 0;
placeholderSprite.visible = index === 0;
// Set scale similar to real sprites
placeholderSprite.scale.set(1);
placeholderSprite.baseScale = 1;
// Mark as placeholder and outside visibility window
placeholderSprite._isPlaceholder = true;
placeholderSprite._placeholderIndex = index;
placeholderSprite._inVisibilityWindow = false;
// Add to container and store reference
slidesContainer.addChild(placeholderSprite);
pixi.slides.current.push(placeholderSprite);
if (isDevelopment) ;
}
}
catch (error) {
if (isDevelopment) ;
}
}
setIsLoading(false);
setLoadingProgress(100);
}
catch (error) {
setIsLoading(false);
}
};
/**
* Helper to create a slide sprite from a texture
*/
const createSlideFromTexture = (texture, imagePath, index, slidesContainer, app, sliderWidth, sliderHeight) => {
try {
// Track texture with resource manager if available
if (resourceManager) {
resourceManager.trackTexture(imagePath, texture);
}
// Create the sprite
const sprite = new Sprite(texture);
sprite.anchor.set(0.5);
sprite.x = app.screen.width / 2;
sprite.y = app.screen.height / 2;
// Set initial state - only show the first slide
sprite.alpha = index === 0 ? 1 : 0;
sprite.visible = index === 0;
// Calculate and apply scale
try {
const { scale, baseScale } = calculateSpriteScale(texture.width, texture.height, sliderWidth, sliderHeight);
sprite.scale.set(scale);
sprite.baseScale = baseScale;
}
catch (scaleError) {
if (isDevelopment) ;
// Fallback scaling
sprite.scale.set(1);
sprite.baseScale = 1;
}
// Track the sprite with resource manager if available
if (resourceManager) {
resourceManager.trackDisplayObject(sprite);
}
// Add to container and store reference
slidesContainer.addChild(sprite);
pixi.slides.current.push(sprite);
if (isDevelopment) ;
return sprite;
}
catch (error) {
return null;
}
};
/**
* Utility function to load a slide at a specific index
* @param index Index of the slide to load
* @returns Promise that resolves when the slide is loaded
*/
const loadSlideAtIndex = async (index) => {
try {
// Validate index
if (index < 0 || index >= props.images.length) {
if (isDevelopment) ;
return false;
}
// Get the current slide sprite
const sprite = pixi.slides.current[index];
// Skip if sprite doesn't exist or is not a placeholder or already loaded
if (!sprite || !sprite._isPlaceholder || sprite._loadingState === 'loaded') {
return true;
}
if (isDevelopment) ;
// Update loading state
sprite._loadingState = 'loading';
// Get image path
const imagePath = props.images[index];
// Determine loading method based on atlas availability
const useAtlas = atlasManager && props.slidesAtlas && areAssetsInAtlas() && isUseSlidesAtlasEnabled();
let texture;
if (useAtlas) {
// Load from atlas
const normalizedPath = normalizePath(imagePath);
const atlasTexture = atlasManager.getFrameTexture(normalizedPath, props.slidesAtlas);
if (!atlasTexture) {
throw new Error(`Frame ${normalizedPath} not found in atlas ${props.slidesAtlas}`);
}
texture = atlasTexture;
}
else {
// Load from individual image
texture = await Assets.load(imagePath);
}
// Store original texture for potential reuse
sprite._originalTexture = texture;
// Apply the texture
sprite.texture = texture;
// Calculate and apply scale
if (sliderRef.current) {
const sliderWidth = sliderRef.current.clientWidth;
const sliderHeight = sliderRef.current.clientHeight;
try {
const { scale, baseScale } = calculateSpriteScale(texture.width, texture.height, sliderWidth, sliderHeight);
sprite.scale.set(scale);
sprite.baseScale = baseScale;
}
catch (scaleError) {
if (isDevelopment) ;
// Fallback scaling
sprite.scale.set(1);
sprite.baseScale = 1;
}
}
// Update flags
sprite._isPlaceholder = false;
sprite._inVisibilityWindow = true;
sprite._loadingState = 'loaded';
// Track the texture and update sprite in resource manager
if (resourceManager) {
resourceManager.trackTexture(imagePath, texture);
resourceManager.trackDisplayObject(sprite);
}
if (isDevelopment) ;
return true;
}
catch (error) {
// Update loading state to error
const sprite = pixi.slides.current[index];
if (sprite) {
sprite._loadingState = 'error';
}
return false;
}
};
/**
* Enhanced transition function with better resource management and animation coordination
*/
const transitionToSlide = useCallback((nextIndex) => {
// Check if slider reference is available
if (!sliderRef.current) {
return null;
}
// Validate inputs
if (!pixi.slides.current.length) {
return null;
}
if (nextIndex < 0 || nextIndex >= pixi.slides.current.length) {
return null;
}
try {
// Cancel any active transition
if (activeTransitionRef.current) {
activeTransitionRef.current.kill();
activeTransitionRef.current = null;
}
if (isDevelopment) ;
const currentIndex = pixi.currentIndex.current;
const currentSlide = pixi.slides.current[currentIndex];
const nextSlide = pixi.slides.current[nextIndex];
// Update sliding window when changing slides
if (slidingWindowManager) {
// Update the central index in the sliding window
slidingWindowManager.updateCurrentIndex(nextIndex);
// Get the new visibility window
const visibilityIndices = slidingWindowManager.getWindowIndices();
if (isDevelopment) ;
// Preload all slides in the visibility window
// We do this asynchronously but don't wait for it
Promise.all(visibilityIndices.map(async (index) => {
// If the slide is a placeholder, load it
const slideSprite = pixi.slides.current[index];
if (slideSprite && slideSprite._isPlaceholder) {
return loadSlideAtIndex(index);
}
return Promise.resolve(true);
})).then((results) => {
if (isDevelopment) ;
});
}
// Check if the next slide is a placeholder, and if so, load it first
const isNextSlideAPlaceholder = nextSlide._isPlaceholder === true;
if (isNextSlideAPlaceholder) {
if (isDevelopment) ;
// Return a promise that resolves with the timeline after loading
return new Promise(async (resolve) => {
try {
// Load the slide
const loadSuccess = await loadSlideAtIndex(nextIndex);
if (!loadSuccess) {
console.error(`Failed to load next slide at index ${nextIndex}`);
resolve(null);
return;
}
// Continue with the transition
const timeline = performTransition(currentIndex, nextIndex);
resolve(timeline);
}
catch (error) {
console.error('Error loading next slide:', error);
resolve(null);
}
});
}
// If next slide is already loaded, directly perform the transition
return performTransition(currentIndex, nextIndex);
}
catch (error) {
return null;
}
}, [
sliderRef,
pixi.slides,
pixi.textContainers,
pixi.currentIndex,
props.transitionScaleIntensity,
resourceManager,
onSlideChange,
animationCoordinator,
slidingWindowManager
]);
/**
* Helper function to perform the actual transition animation between slides
*/
const performTransition = (currentIndex, nextIndex) => {
try {
const currentSlide = pixi.slides.current[currentIndex];
const nextSlide = pixi.slides.current[nextIndex];
// Handle text containers if available
const textContainersAvailable = pixi.textContainers.current &&
pixi.textContainers.current.length > currentIndex &&
pixi.textContainers.current.length > nextIndex;
const currentTextContainer = textContainersAvailable
? pixi.textContainers.current[currentIndex]
: null;
const nextTextContainer = textContainersAvailable
? pixi.textContainers.current[nextIndex]
: null;
// IMPORTANT: Make both slides visible during transition
currentSlide.visible = true;
nextSlide.visible = true;
// Ensure next elements start invisible (alpha = 0)
nextSlide.alpha = 0;
if (nextTextContainer) {
nextTextContainer.alpha = 0;
nextTextContainer.visible = true; // Make next text visible before transition
}
// Calculate scale based on transition intensity
const transitionScaleIntensity = props.transitionScaleIntensity ?? 30;
const scaleMultiplier = 1 + transitionScaleIntensity / 100;
// Create animations for slide transitions
const slideOutAnimations = [];
const slideInAnimations = [];
const textOutAnimations = [];
const textInAnimations = [];
// Create slide out animations
slideOutAnimations.push(gsap.to(currentSlide.scale, {
x: currentSlide.baseScale * scaleMultiplier,
y: currentSlide.baseScale * scaleMultiplier,
duration: 1,
ease: 'power2.out',
onComplete: () => {
// Re-track the sprite after animation
if (resourceManager) {
resourceManager.trackDisplayObject(currentSlide);
}
}
}), gsap.to(currentSlide, {
alpha: 0,
duration: 1,
ease: 'power2.out',
onComplete: () => {
// IMPORTANT: Hide previous slide after transition completes to save GPU
currentSlide.visible = false;
// Re-track the sprite after visibility change
if (resourceManager) {
resourceManager.trackDisplayObject(currentSlide);
}
}
}));
// Create slide in animations
slideInAnimations.push(gsap.to(nextSlide.scale, {
x: nextSlide.baseScale,
y: nextSlide.baseScale,
duration: 1,
ease: 'power2.out',
onComplete: () => {
// Re-track the sprite after animation
if (resourceManager) {
resourceManager.trackDisplayObject(nextSlide);
}
}
}), gsap.to(nextSlide, {
alpha: 1,
duration: 1,
ease: 'power2.out',
onComplete: () => {
// Re-track the sprite after animation
if (resourceManager) {
resourceManager.trackDisplayObject(nextSlide);
}
}
}));
// Set initial scale for next slide
nextSlide.scale.set(nextSlide.baseScale * scaleMultiplier, nextSlide.baseScale * scaleMultiplier);
// Add text container animations if available
if (currentTextContainer && nextTextContainer) {
textOutAnimations.push(gsap.to(currentTextContainer, {
alpha: 0,
duration: 1,
ease: 'power2.out',
onComplete: () => {
// Hide previous text after transition
currentTextContainer.visible = false;
// Re-track the container after visibility change
if (resourceManager) {
resourceManager.trackDisplayObject(currentTextContainer);
}
}
}));
textInAnimations.push(gsap.to(nextTextContainer, {
alpha: 1,
duration: 1,
ease: 'power2.out',
onComplete: () => {
// Re-track the container after animation
if (resourceManager) {
resourceManager.trackDisplayObject(nextTextContainer);
}
}
}));
}
// Use the AnimationCoordinator to create coordinated animation groups
const slideOutGroup = {
id: `slide_out_${currentIndex}_${Date.now()}`,
type: AnimationGroupType.SLIDE_TRANSITION,
animations: slideOutAnimations
};
const slideInGroup = {
id: `slide_in_${nextIndex}_${Date.now()}`,
type: AnimationGroupType.SLIDE_TRANSITION,
animations: slideInAnimations
};
// Create a master timeline for the entire transition
const masterTimeline = gsap.timeline({
onComplete: () => {
// Update current index when transition completes
pixi.currentIndex.current = nextIndex;
activeTransitionRef.current = null;
// Call the onSlideChange callback if provided
if (onSlideChange) {
onSlideChange(nextIndex);
}
// If using sliding window, check if any slides should be unloaded
if (slidingWindowManager) {
handleSlidingWindowUnload(nextIndex);
}
}
});
// Add slide animations to the master timeline
const slideOutTimeline = animationCoordinator.createAnimationGroup(slideOutGroup);
const slideInTimeline = animationCoordinator.createAnimationGroup(slideInGroup);
masterTimeline.add(slideOutTimeline, 0);
masterTimeline.add(slideInTimeline, 0);
// Add text animations if available
if (textOutAnimations.length > 0 && textInAnimations.length > 0) {
const textOutGroup = {
id: `text_out_${currentIndex}_${Date.now()}`,
type: AnimationGroupType.TEXT_ANIMATION,
animations: textOutAnimations
};
const textInGroup = {
id: `text_in_${nextIndex}_${Date.now()}`,
type: AnimationGroupType.TEXT_ANIMATION,
animations: textInAnimations
};
const textOutTimeline = animationCoordinator.createAnimationGroup(textOutGroup);
const textInTimeline = animationCoordinator.createAnimationGroup(textInGroup);
masterTimeline.add(textOutTimeline, 0);
masterTimeline.add(textInTimeline, 0);
}
// Store the master timeline
activeTransitionRef.current = masterTimeline;
// Track the master timeline with resourceManager
if (resourceManager) {
resourceManager.trackAnimation(masterTimeline);
}
return masterTimeline;
}
catch (error) {
return null;
}
};
/**
* Helper function to handle unloading slides that are far outside the visibility window
*/
const handleSlidingWindowUnload = (currentIndex) => {
if (!slidingWindowManager)
return;
// Get current visibility window
const visibilityIndices = slidingWindowManager.getWindowIndices();
// Check all loaded slides and unload those that are far outside the visibility window
pixi.slides.current.forEach((sprite, index) => {
// Skip if sprite is already a placeholder or uninitialized
if (sprite._isPlaceholder || sprite._loadingState === 'uninitialized') {
return;
}
// If this slide is outside the visibility window and not the current slide
if (!visibilityIndices.includes(index) && index !== currentIndex) {
// How far outside the window is this slide?
const distanceFromWindow = Math.min(...visibilityIndices.map(visIndex => Math.abs(index - visIndex)));
// Only unload if it's far enough away (e.g., more than 2 slides away from any visible slide)
const unloadThreshold = slidingWindowManager.getWindowSize() + 1;
if (distanceFromWindow > unloadThreshold) {
// Don't unload slides that are very close to the current index
const distanceFromCurrent = Math.abs(index - currentIndex);
if (distanceFromCurrent <= unloadThreshold) {
return;
}
// Import placeholder utilities
import('../utils/placeholderUtils.js').then(({ createPlaceholderSprite }) => {
// Create placeholder to replace the loaded sprite
if (sliderRef.current && pixi.app.current) {
const sliderWidth = sliderRef.current.clientWidth;
const sliderHeight = sliderRef.current.clientHeight;
const placeholderOptions = {
width: sliderWidth,
height: sliderHeight,
color: 0x333333,
showIndex: isDevelopment,
index,
trackWithResourceManager: true,
resourceManager,
renderer: pixi.app.current.renderer
};
// Create a placeholder
const placeholderSprite = createPlaceholderSprite(placeholderOptions);
// Copy position and other properties
placeholderSprite.x = sprite.x;
placeholderSprite.y = sprite.y;
placeholderSprite.alpha = sprite.alpha;
placeholderSprite.visible = sprite.visible;
placeholderSprite.baseScale = sprite.baseScale;
placeholderSprite.scale.set(sprite.scale.x, sprite.scale.y);
// Mark as outside visibility window
placeholderSprite._inVisibilityWindow = false;
// Store original texture for potential reuse
placeholderSprite._originalTexture = sprite.texture;
// Replace in the container
if (sprite.parent) {
const parent = sprite.parent;
const spriteIndex = parent.getChildIndex(sprite);
parent.addChildAt(placeholderSprite, spriteIndex);
parent.removeChild(sprite);
}
// Replace in the slides array
pixi.slides.current[index] = placeholderSprite;
// Properly dispose the sprite (but keep the texture)
if (resourceManager) {
// First, remove from ResourceManager's tracking
resourceManager.trackDisplayObject(sprite);
// Then destroy the sprite, but keep the texture
sprite.destroy({ children: true, texture: false });
// At this point, the sprite is gone and ResourceManager won't find it during cleanup
}
else {
// If no resource manager, just destroy
sprite.destroy({ children: true, texture: false });
}
}
});
}
}
});
};
// Add nextSlide and prevSlide methods
const nextSlide = useCallback((nextIndex) => {
const tl = transitionToSlide(nextIndex);
if (tl && onSlideChange) {
onSlideChange(nextIndex);
}
}, [transitionToSlide, onSlideChange]);
const prevSlide = useCallback((prevIndex) => {
const tl = transitionToSlide(prevIndex);
if (tl && onSlideChange) {
onSlideChange(prevIndex);
}
}, [transitionToSlide, onSlideChange]);
return {
transitionToSlide,
nextSlide,
prevSlide,
isLoading,
loadingProgress
};
};
export { useSlides as default, useSlides };
//# sourceMappingURL=useSlides.js.map