kinetic-slider
Version:
A WebGL-powered kinetic slider component using PIXI.js
241 lines (238 loc) • 8.95 kB
JavaScript
import { useEffect } from 'react';
import { Container, TextStyle, Text } from 'pixi.js';
import { gsap } from 'gsap';
import { setupCustomFonts } from '../utils/fontUtils.js';
const log = {
info: (message, ...args) => {
},
warn: (message, ...args) => {
},
error: (message, error) => {
}
};
const DEFAULT_FONTS = {
title: 'Georgia, Times, "Times New Roman", serif',
subtitle: "Helvetica, Arial, sans-serif"
};
function prepareFontFamily(fontStack, defaultStack = DEFAULT_FONTS.title) {
try {
if (!fontStack) {
log.info(`No font stack provided, using default: ${defaultStack}`);
return defaultStack;
}
const cleanedStack = fontStack.split(",").map((font) => font.trim().replace(/['"]/g, "")).filter(Boolean).join(", ");
log.info(`Processed font stack: ${cleanedStack}`);
return cleanedStack || defaultStack;
} catch (error) {
return defaultStack;
}
}
const useTextContainers = ({
sliderRef,
appRef,
slidesRef,
textContainersRef,
currentIndex,
buttonMode,
texts,
// Title props
textTitleColor,
textTitleSize,
mobileTextTitleSize,
textTitleLetterspacing,
textTitleFontFamily,
// Subtitle props
textSubTitleColor,
textSubTitleSize,
mobileTextSubTitleSize,
textSubTitleLetterspacing,
textSubTitleOffsetTop,
mobileTextSubTitleOffsetTop,
textSubTitleFontFamily,
resourceManager
}) => {
const cancellationRef = { current: { isCancelled: false } };
useEffect(() => {
if (typeof window === "undefined") return;
cancellationRef.current.isCancelled = false;
if (!appRef.current || !slidesRef.current.length || !texts.length) {
log.warn("Initialization aborted due to missing references");
return;
}
const setupFontsAndCreateContainers = async () => {
try {
if (textTitleFontFamily || textSubTitleFontFamily) {
log.info("Setting up custom fonts", {
titleFont: textTitleFontFamily,
subtitleFont: textSubTitleFontFamily
});
await setupCustomFonts(textTitleFontFamily, textSubTitleFontFamily);
}
if (cancellationRef.current.isCancelled) return;
const app = appRef.current;
const stage = app.stage.children[0];
textContainersRef.current.forEach((container) => {
try {
if (container.parent) {
container.parent.removeChild(container);
}
} catch (removeError) {
log.error("Error removing existing text container", removeError);
}
});
textContainersRef.current = [];
const isMobile = window.innerWidth < 768;
const computedTitleSize = isMobile ? mobileTextTitleSize : textTitleSize;
const computedSubTitleSize = isMobile ? mobileTextSubTitleSize : textSubTitleSize;
const computedSubTitleOffset = isMobile ? mobileTextSubTitleOffsetTop : textSubTitleOffsetTop;
const titleFontFamily = prepareFontFamily(textTitleFontFamily);
const subtitleFontFamily = prepareFontFamily(textSubTitleFontFamily, DEFAULT_FONTS.subtitle);
log.info("Creating text containers", {
titleFont: titleFontFamily,
subtitleFont: subtitleFontFamily
});
texts.forEach((textPair, index) => {
const [title, subtitle] = textPair;
const textContainer = new Container();
textContainer.x = app.screen.width / 2;
textContainer.y = app.screen.height / 2;
if (resourceManager) {
resourceManager.trackDisplayObject(textContainer);
}
const titleStyle = new TextStyle({
fill: textTitleColor,
fontSize: computedTitleSize,
letterSpacing: textTitleLetterspacing,
fontWeight: "bold",
align: "center",
fontFamily: titleFontFamily
});
const titleText = new Text({ text: title, style: titleStyle });
titleText.anchor.set(0.5, 0);
titleText.y = 0;
if (resourceManager) {
resourceManager.trackDisplayObject(titleText);
}
const subtitleStyle = new TextStyle({
fill: textSubTitleColor,
fontSize: computedSubTitleSize,
letterSpacing: textSubTitleLetterspacing,
align: "center",
fontFamily: subtitleFontFamily
});
const subText = new Text({ text: subtitle, style: subtitleStyle });
subText.anchor.set(0.5, 0);
subText.y = titleText.height + computedSubTitleOffset;
if (resourceManager) {
resourceManager.trackDisplayObject(subText);
}
textContainer.addChild(titleText, subText);
textContainer.pivot.y = textContainer.height / 2;
textContainer.alpha = index === 0 ? 1 : 0;
textContainer.visible = index === 0;
if (buttonMode) {
textContainer.eventMode = "static";
textContainer.cursor = "pointer";
textContainer.on("pointerover", () => {
gsap.to(titleText.scale, {
x: 1.1,
y: 1.1,
duration: 0.2,
onComplete: () => {
if (resourceManager) {
resourceManager.trackDisplayObject(titleText);
}
}
});
});
textContainer.on("pointerout", () => {
gsap.to(titleText.scale, {
x: 1,
y: 1,
duration: 0.2,
onComplete: () => {
if (resourceManager) {
resourceManager.trackDisplayObject(titleText);
}
}
});
});
textContainer.on("pointerdown", () => {
const nextIndex = (currentIndex.current + 1) % slidesRef.current.length;
window.dispatchEvent(new CustomEvent("slideChange", {
detail: { nextIndex }
}));
});
}
stage.addChild(textContainer);
textContainersRef.current.push(textContainer);
});
log.info(`Created ${texts.length} text containers`);
} catch (error) {
}
};
setupFontsAndCreateContainers();
const handleResize = () => {
try {
if (cancellationRef.current.isCancelled || !appRef.current || !sliderRef.current || !textContainersRef.current.length) return;
const containerWidth = sliderRef.current.clientWidth || 0;
const containerHeight = sliderRef.current.clientHeight || 0;
const isMobile = window.innerWidth < 768;
const computedTitleSize = isMobile ? mobileTextTitleSize : textTitleSize;
const computedSubTitleSize = isMobile ? mobileTextSubTitleSize : textSubTitleSize;
const computedSubTitleOffset = isMobile ? mobileTextSubTitleOffsetTop : textSubTitleOffsetTop;
const titleFontFamily = prepareFontFamily(textTitleFontFamily);
const subtitleFontFamily = prepareFontFamily(textSubTitleFontFamily, DEFAULT_FONTS.subtitle);
textContainersRef.current.forEach((container) => {
container.x = containerWidth / 2;
container.y = containerHeight / 2;
const titleText = container.children[0];
titleText.style.fontSize = computedTitleSize;
titleText.style.fontFamily = titleFontFamily;
const titleContent = titleText.text;
titleText.text = titleContent;
const subText = container.children[1];
subText.style.fontSize = computedSubTitleSize;
subText.style.fontFamily = subtitleFontFamily;
subText.y = titleText.height + computedSubTitleOffset;
const subContent = subText.text;
subText.text = subContent;
container.pivot.y = container.height / 2;
if (resourceManager) {
resourceManager.trackDisplayObject(container);
resourceManager.trackDisplayObject(titleText);
resourceManager.trackDisplayObject(subText);
}
});
log.info("Text containers resized");
} catch (error) {
}
};
window.addEventListener("resize", handleResize);
handleResize();
return () => {
cancellationRef.current.isCancelled = true;
window.removeEventListener("resize", handleResize);
};
}, [
// Dependency array with all props for comprehensive updates
appRef.current,
texts,
textTitleColor,
textTitleSize,
mobileTextTitleSize,
textTitleLetterspacing,
textTitleFontFamily,
textSubTitleColor,
textSubTitleSize,
mobileTextSubTitleSize,
textSubTitleLetterspacing,
textSubTitleOffsetTop,
mobileTextSubTitleOffsetTop,
textSubTitleFontFamily,
buttonMode,
resourceManager
]);
};
export { useTextContainers as default };
//# sourceMappingURL=useTextContainers.js.map