kinetic-slider
Version:
A WebGL-powered kinetic slider component using PIXI.js
414 lines (410 loc) • 14.2 kB
JavaScript
'use strict';
var react = require('react');
var pixi_js = require('pixi.js');
var gsap = require('gsap');
var RenderScheduler = require('../managers/RenderScheduler.cjs');
var UpdateTypes = require('../managers/UpdateTypes.cjs');
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 = react.useRef({
isInitializing: false,
isInitialized: false
});
const validateDimensions = react.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 = react.useCallback(async (imagePath) => {
if (!imagePath || typeof imagePath !== "string" || imagePath.trim() === "") {
throw new Error("Invalid image path");
}
try {
let texture = null;
let loadingMethod = "";
if (pixi_js.Assets.cache.has(imagePath)) {
texture = pixi_js.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 pixi_js.Assets.load(imagePath);
loadingMethod = "direct-load";
} catch (loadError) {
if (isDevelopment) ;
const fallbackPath = imagePath.split("/").pop();
if (fallbackPath && fallbackPath !== imagePath) {
try {
texture = await pixi_js.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 = react.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 pixi_js.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 pixi_js.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 pixi_js.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 pixi_js.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
]);
react.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 = react.useCallback(() => {
if (!initializationStateRef.current.isInitialized) return [];
const scheduler = RenderScheduler.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.gsap.to(bgSprite, {
alpha: 1,
duration: 0.5
});
const bgFilterAnim = gsap.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.gsap.to(cursorSprite, {
alpha: 1,
duration: 0.5
});
const cursorFilterAnim = gsap.gsap.to(cursorFilter.scale, {
x: DEFAULT_CURSOR_FILTER_SCALE,
y: DEFAULT_CURSOR_FILTER_SCALE,
duration: 0.5
});
animations.push(cursorAlphaAnim, cursorFilterAnim);
}
}
scheduler.scheduleTypedUpdate(
"displacementEffects",
UpdateTypes.UpdateType.DISPLACEMENT_EFFECT,
() => {
},
"critical"
);
if (resourceManager && animations.length) {
resourceManager.trackAnimationBatch(animations);
}
return animations;
};
return animate();
}, [
backgroundDisplacementSpriteRef,
bgDispFilterRef,
cursorDisplacementSpriteRef,
cursorDispFilterRef,
cursorImgEffect,
resourceManager
]);
const hideDisplacementEffects = react.useCallback(() => {
if (!initializationStateRef.current.isInitialized) return [];
RenderScheduler.RenderScheduler.getInstance();
const animate = () => {
const animations = [];
const bgSprite = backgroundDisplacementSpriteRef.current;
const bgFilter = bgDispFilterRef.current;
if (bgSprite && bgFilter) {
const bgAlphaAnim = gsap.gsap.to(bgSprite, {
alpha: 0,
duration: 0.5
});
const bgFilterAnim = gsap.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.gsap.to(cursorSprite, {
alpha: 0,
duration: 0.5
});
const cursorFilterAnim = gsap.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
]);
react.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
};
};
exports.useDisplacementEffects = useDisplacementEffects;
//# sourceMappingURL=useDisplacementEffects.cjs.map