scradar
Version:
CSS-first scroll interaction library with progress-based animations
168 lines (141 loc) • 4.41 kB
JavaScript
import { useEffect, useRef, useCallback, useMemo } from 'react';
import Scradar from '../../src/scradar.js';
// Global Scradar instance for performance optimization
let globalScradar = null;
let instanceCount = 0;
// Cleanup function to destroy global instance when no components are using it
const cleanupGlobalInstance = () => {
if (globalScradar && instanceCount === 0) {
globalScradar.destroy();
globalScradar = null;
}
};
export function useScradar(options = {}) {
const scradarRef = useRef(null);
const optionsRef = useRef(options);
const isInitializedRef = useRef(false);
// Memoize options to prevent unnecessary re-initialization
const memoizedOptions = useMemo(() => options, [
options.target,
options.debug,
options.boundary,
options.totalProgress,
options.momentum
]);
// Initialize Scradar
const initializeScradar = useCallback(() => {
if (isInitializedRef.current) return;
// Use global instance for better performance in SPA
if (!globalScradar) {
globalScradar = new Scradar(memoizedOptions);
} else {
// Update existing instance with new options
globalScradar.update();
}
instanceCount++;
scradarRef.current = globalScradar;
isInitializedRef.current = true;
}, [memoizedOptions]);
// Cleanup function
const cleanup = useCallback(() => {
if (isInitializedRef.current) {
instanceCount--;
isInitializedRef.current = false;
cleanupGlobalInstance();
}
}, []);
// Initialize on mount
useEffect(() => {
initializeScradar();
return cleanup;
}, [initializeScradar, cleanup]);
// Update when options change
useEffect(() => {
if (scradarRef.current && optionsRef.current !== options) {
optionsRef.current = options;
scradarRef.current.update();
}
}, [options]);
// Handle route changes in SPA
useEffect(() => {
const handleRouteChange = () => {
if (scradarRef.current) {
// Small delay to ensure DOM is updated
setTimeout(() => {
scradarRef.current.update();
}, 100);
}
};
// Listen for route changes (works with most React routers)
window.addEventListener('popstate', handleRouteChange);
// For React Router v6
if (window.history && window.history.pushState) {
const originalPushState = window.history.pushState;
window.history.pushState = function(...args) {
originalPushState.apply(this, args);
handleRouteChange();
};
}
return () => {
window.removeEventListener('popstate', handleRouteChange);
};
}, []);
return scradarRef.current;
}
// Hook for individual elements
export function useScradarElement(config = {}, dependencies = []) {
const elementRef = useRef(null);
const configRef = useRef(config);
const scradarElement = useCallback((node) => {
if (node) {
elementRef.current = node;
// Apply configuration
if (configRef.current) {
node.setAttribute('data-scradar', JSON.stringify(configRef.current));
}
}
}, [dependencies]);
// Update configuration when it changes
useEffect(() => {
if (elementRef.current && configRef.current !== config) {
configRef.current = config;
elementRef.current.setAttribute('data-scradar', JSON.stringify(config));
// Trigger update if global instance exists
if (globalScradar) {
globalScradar.update();
}
}
}, [config, dependencies]);
return scradarElement;
}
// Hook for managing global configurations
export function useScradarConfigs(configs = {}) {
useEffect(() => {
Scradar.configs = { ...Scradar.configs, ...configs };
// Update existing instance if available
if (globalScradar) {
globalScradar.update();
}
}, [configs]);
return useCallback((newConfigs) => {
Scradar.configs = { ...Scradar.configs, ...newConfigs };
if (globalScradar) {
globalScradar.update();
}
}, []);
}
// Utility function for manual control
export function setScradarConfigs(configs) {
Scradar.configs = { ...Scradar.configs, ...configs };
if (globalScradar) {
globalScradar.update();
}
}
// Cleanup function for manual cleanup (useful in tests)
export function cleanupScradar() {
if (globalScradar) {
globalScradar.destroy();
globalScradar = null;
instanceCount = 0;
}
}