UNPKG

kinetic-slider

Version:

A WebGL-powered kinetic slider component using PIXI.js

412 lines (409 loc) 14 kB
import { useRef, useCallback, useEffect } from 'react'; import { Assets, Sprite, DisplacementFilter } from 'pixi.js'; import { gsap } from 'gsap'; import { RenderScheduler } from '../managers/RenderScheduler.js'; import { UpdateType } from '../managers/UpdateTypes.js'; const isDevelopment = false; const DEFAULT_BG_FILTER_SCALE = 20; const DEFAULT_CURSOR_FILTER_SCALE = 10; const useDisplacementEffects = ({ sliderRef, bgDispFilterRef, cursorDispFilterRef, backgroundDisplacementSpriteRef, cursorDisplacementSpriteRef, appRef, backgroundDisplacementSpriteLocation, cursorDisplacementSpriteLocation, cursorImgEffect, cursorScaleIntensity = 0.65, cursorDisplacementSizing = "natural", cursorDisplacementWidth, cursorDisplacementHeight, resourceManager, atlasManager, effectsAtlas, useEffectsAtlas }) => { const initializationStateRef = useRef({ isInitializing: false, isInitialized: false }); const validateDimensions = useCallback((width, height, textureWidth, textureHeight) => { let result = { width: width || textureWidth, height: height || textureHeight, isValid: true }; if (width !== void 0 && width <= 0 || height !== void 0 && height <= 0) { result = { width: textureWidth, height: textureHeight, isValid: false }; } const app = appRef.current; if (app && (width && width > app.screen.width * 10 || height && height > app.screen.height * 10)) ; return result; }, [appRef]); const loadTexture = useCallback(async (imagePath) => { if (!imagePath || typeof imagePath !== "string" || imagePath.trim() === "") { throw new Error("Invalid image path"); } try { let texture = null; let loadingMethod = ""; if (Assets.cache.has(imagePath)) { texture = Assets.cache.get(imagePath); loadingMethod = "cache"; } else if (atlasManager && effectsAtlas && useEffectsAtlas) { const frameName = imagePath.split("/").pop() || ""; if (atlasManager.hasFrame(frameName)) { const atlasTexture = atlasManager.getFrameTexture(frameName, effectsAtlas); if (atlasTexture) { texture = atlasTexture; loadingMethod = "atlas"; } } } if (!texture) { try { texture = await Assets.load(imagePath); loadingMethod = "direct-load"; } catch (loadError) { if (isDevelopment) ; const fallbackPath = imagePath.split("/").pop(); if (fallbackPath && fallbackPath !== imagePath) { try { texture = await Assets.load(fallbackPath); loadingMethod = "fallback-path"; } catch (fallbackError) { throw loadError; } } else { throw loadError; } } } if (!texture) { throw new Error(`Failed to load texture: ${imagePath}`); } if (isDevelopment) ; return texture; } catch (error) { const enhancedError = new Error(`Failed to load texture: ${imagePath}. ${error}`); throw enhancedError; } }, [atlasManager, effectsAtlas, useEffectsAtlas]); const setupDisplacementEffects = useCallback(async () => { if (initializationStateRef.current.isInitializing || initializationStateRef.current.isInitialized) { return; } initializationStateRef.current.isInitializing = true; try { const stage = appRef.current?.stage; if (!stage) { throw new Error("Stage not available"); } const canvasWidth = appRef.current?.screen.width ?? 0; const canvasHeight = appRef.current?.screen.height ?? 0; if (canvasWidth === 0 || canvasHeight === 0) { throw new Error("Invalid canvas dimensions"); } const bgTexture = await loadTexture(backgroundDisplacementSpriteLocation); const bgSprite = new Sprite(bgTexture); const bgScaleX = canvasWidth / bgTexture.width; const bgScaleY = canvasHeight / bgTexture.height; bgSprite.scale.set(bgScaleX, bgScaleY); bgSprite.anchor.set(0.5); bgSprite.position.set(canvasWidth / 2, canvasHeight / 2); bgSprite.renderable = false; bgSprite.visible = true; bgSprite.alpha = 0; const bgFilter = new DisplacementFilter(bgSprite); bgFilter.scale.set(0); bgFilter.padding = 0; backgroundDisplacementSpriteRef.current = bgSprite; bgDispFilterRef.current = bgFilter; stage.addChild(bgSprite); if (!stage.filters) { stage.filters = [bgFilter]; } else if (!Array.isArray(stage.filters)) { stage.filters = [stage.filters, bgFilter]; } else { stage.filters = [...stage.filters, bgFilter]; } if (isDevelopment) ; if (resourceManager) { resourceManager.trackDisplayObject(bgSprite); resourceManager.trackFilter(bgFilter); } if (cursorImgEffect) { const cursorTexture = await loadTexture(cursorDisplacementSpriteLocation); const cursorSprite = new Sprite(cursorTexture); let cursorScaleX = 1; let cursorScaleY = 1; if (cursorDisplacementSizing) { const validatedDimensions = validateDimensions( cursorDisplacementWidth, cursorDisplacementHeight, cursorTexture.width, cursorTexture.height ); if (cursorDisplacementSizing === "fullscreen") { cursorScaleX = canvasWidth / cursorTexture.width; cursorScaleY = canvasHeight / cursorTexture.height; cursorSprite.anchor.set(0.5); cursorSprite.position.set(canvasWidth / 2, canvasHeight / 2); if (isDevelopment) ; } else if (validatedDimensions.width && validatedDimensions.height) { cursorScaleX = validatedDimensions.width / cursorTexture.width; cursorScaleY = validatedDimensions.height / cursorTexture.height; cursorSprite.anchor.set(0.5); cursorSprite.position.set( validatedDimensions.width / 2, validatedDimensions.height / 2 ); if (isDevelopment) ; } else { cursorScaleX = 1; cursorScaleY = 1; if (isDevelopment) ; } } else { cursorScaleX = 1; cursorScaleY = 1; if (isDevelopment) ; } cursorSprite.scale.set( cursorScaleX * cursorScaleIntensity, cursorScaleY * cursorScaleIntensity ); cursorSprite.renderable = false; cursorSprite.visible = true; cursorSprite.alpha = 0; const cursorFilter = new DisplacementFilter(cursorSprite); cursorFilter.scale.set(0); cursorFilter.padding = 0; cursorDisplacementSpriteRef.current = cursorSprite; cursorDispFilterRef.current = cursorFilter; stage.addChild(cursorSprite); if (!stage.filters) { stage.filters = [cursorFilter]; } else if (!Array.isArray(stage.filters)) { stage.filters = [stage.filters, cursorFilter]; } else { stage.filters = [...stage.filters, cursorFilter]; } if (isDevelopment) ; if (resourceManager) { resourceManager.trackDisplayObject(cursorSprite); resourceManager.trackFilter(cursorFilter); } } initializationStateRef.current = { isInitializing: false, isInitialized: true }; if (isDevelopment) ; } catch (error) { initializationStateRef.current = { isInitializing: false, isInitialized: false }; throw error; } }, [ appRef, backgroundDisplacementSpriteLocation, cursorDisplacementSpriteLocation, cursorImgEffect, cursorScaleIntensity, cursorDisplacementSizing, cursorDisplacementWidth, cursorDisplacementHeight, resourceManager, atlasManager, effectsAtlas, useEffectsAtlas, validateDimensions, loadTexture ]); useEffect(() => { if (typeof window === "undefined") return; const handleResize = () => { const app = appRef.current; if (!app) return; const canvasWidth = app.screen.width; const canvasHeight = app.screen.height; const bgSprite = backgroundDisplacementSpriteRef.current; if (bgSprite && bgSprite.texture) { bgSprite.position.set(canvasWidth / 2, canvasHeight / 2); const bgScaleX = canvasWidth / bgSprite.texture.width; const bgScaleY = canvasHeight / bgSprite.texture.height; bgSprite.scale.set(bgScaleX, bgScaleY); } if (cursorImgEffect && cursorDisplacementSizing === "fullscreen") { const cursorSprite = cursorDisplacementSpriteRef.current; if (cursorSprite && cursorSprite.texture) { cursorSprite.position.set(canvasWidth / 2, canvasHeight / 2); const scaleX = canvasWidth / cursorSprite.texture.width; const scaleY = canvasHeight / cursorSprite.texture.height; cursorSprite.scale.set( scaleX * cursorScaleIntensity, scaleY * cursorScaleIntensity ); } } }; window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, [ appRef, backgroundDisplacementSpriteRef, cursorDisplacementSpriteRef, cursorDisplacementSizing, cursorImgEffect, cursorScaleIntensity ]); const showDisplacementEffects = useCallback(() => { if (!initializationStateRef.current.isInitialized) return []; const scheduler = RenderScheduler.getInstance(); const animate = () => { const animations = []; const bgSprite = backgroundDisplacementSpriteRef.current; const bgFilter = bgDispFilterRef.current; if (bgSprite && bgFilter) { bgSprite.visible = true; bgSprite.renderable = false; bgFilter.scale.x = DEFAULT_BG_FILTER_SCALE; bgFilter.scale.y = DEFAULT_BG_FILTER_SCALE; const bgAlphaAnim = gsap.to(bgSprite, { alpha: 1, duration: 0.5 }); const bgFilterAnim = gsap.to(bgFilter.scale, { x: DEFAULT_BG_FILTER_SCALE, y: DEFAULT_BG_FILTER_SCALE, duration: 0.5 }); animations.push(bgAlphaAnim, bgFilterAnim); } if (cursorImgEffect) { const cursorSprite = cursorDisplacementSpriteRef.current; const cursorFilter = cursorDispFilterRef.current; if (cursorSprite && cursorFilter) { cursorSprite.visible = true; cursorSprite.renderable = false; cursorFilter.scale.x = DEFAULT_CURSOR_FILTER_SCALE; cursorFilter.scale.y = DEFAULT_CURSOR_FILTER_SCALE; const cursorAlphaAnim = gsap.to(cursorSprite, { alpha: 1, duration: 0.5 }); const cursorFilterAnim = gsap.to(cursorFilter.scale, { x: DEFAULT_CURSOR_FILTER_SCALE, y: DEFAULT_CURSOR_FILTER_SCALE, duration: 0.5 }); animations.push(cursorAlphaAnim, cursorFilterAnim); } } scheduler.scheduleTypedUpdate( "displacementEffects", UpdateType.DISPLACEMENT_EFFECT, () => { }, "critical" ); if (resourceManager && animations.length) { resourceManager.trackAnimationBatch(animations); } return animations; }; return animate(); }, [ backgroundDisplacementSpriteRef, bgDispFilterRef, cursorDisplacementSpriteRef, cursorDispFilterRef, cursorImgEffect, resourceManager ]); const hideDisplacementEffects = useCallback(() => { if (!initializationStateRef.current.isInitialized) return []; RenderScheduler.getInstance(); const animate = () => { const animations = []; const bgSprite = backgroundDisplacementSpriteRef.current; const bgFilter = bgDispFilterRef.current; if (bgSprite && bgFilter) { const bgAlphaAnim = gsap.to(bgSprite, { alpha: 0, duration: 0.5 }); const bgFilterAnim = gsap.to(bgFilter.scale, { x: 0, y: 0, duration: 0.5 }); animations.push(bgAlphaAnim, bgFilterAnim); } if (cursorImgEffect) { const cursorSprite = cursorDisplacementSpriteRef.current; const cursorFilter = cursorDispFilterRef.current; if (cursorSprite && cursorFilter) { const cursorAlphaAnim = gsap.to(cursorSprite, { alpha: 0, duration: 0.5 }); const cursorFilterAnim = gsap.to(cursorFilter.scale, { x: 0, y: 0, duration: 0.5 }); animations.push(cursorAlphaAnim, cursorFilterAnim); } } if (resourceManager && animations.length) { resourceManager.trackAnimationBatch(animations); } return animations; }; return animate(); }, [ backgroundDisplacementSpriteRef, bgDispFilterRef, cursorDisplacementSpriteRef, cursorDispFilterRef, cursorImgEffect, resourceManager ]); useEffect(() => { if (typeof window === "undefined") return; if (appRef.current?.stage) { try { setupDisplacementEffects().catch((error) => { if (isDevelopment) ; initializationStateRef.current = { isInitializing: false, isInitialized: false }; }); } catch (error) { } } return () => { initializationStateRef.current = { isInitializing: false, isInitialized: false }; }; }, [appRef.current?.stage, setupDisplacementEffects]); return { showDisplacementEffects, hideDisplacementEffects, DEFAULT_BG_FILTER_SCALE, DEFAULT_CURSOR_FILTER_SCALE }; }; export { useDisplacementEffects }; //# sourceMappingURL=useDisplacementEffects.js.map