165 lines
5 KiB
JavaScript
165 lines
5 KiB
JavaScript
/**
|
|
Initialize GSAP on pages with Turbo frames */
|
|
|
|
import gsap from 'gsap';
|
|
import ScrollTrigger from 'gsap/ScrollTrigger';
|
|
import { shouldAnimate, punchIn, punchOut } from './gsap-animations.js';
|
|
|
|
// Register GSAP plugins
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
|
|
// Register GSDevTools in development only (esbuild inlines ELEVENTY_ENV at build time)
|
|
if (process.env.ELEVENTY_ENV === 'development') {
|
|
try {
|
|
// Dynamic import wrapped in try-catch; GSDevTools package must be installed
|
|
import('gsap/GSDevTools').then(module => {
|
|
gsap.registerPlugin(module.default);
|
|
}).catch(() => {
|
|
// Silently continue if GSDevTools is not available
|
|
});
|
|
} catch (e) {
|
|
// Fallback for any import errors
|
|
}
|
|
}
|
|
|
|
// Store active contexts for cleanup
|
|
const activeContexts = new Map();
|
|
|
|
/**
|
|
* Initialize GSAP animations for all containers
|
|
*/
|
|
function initGsapAnimations() {
|
|
// Check if reduced motion is preferred
|
|
if (!shouldAnimate()) {
|
|
// Skip animations if user prefers reduced motion
|
|
console.log('GSAP animations disabled: prefers-reduced-motion');
|
|
return;
|
|
}
|
|
|
|
// Find all GSAP animation containers
|
|
const containers = document.querySelectorAll('[data-gsap-scroll-anim]');
|
|
|
|
containers.forEach(container => {
|
|
try {
|
|
// Parse configuration from data attribute
|
|
const config = JSON.parse(container.dataset.gsapScrollAnim);
|
|
|
|
// Extract configuration with defaults
|
|
const {
|
|
animationType,
|
|
scrollStart = 'top 80%',
|
|
scrollEnd = 'bottom 20%',
|
|
scrub = true,
|
|
pin = true,
|
|
markers = false
|
|
} = config;
|
|
|
|
// Find all animated items within container
|
|
const items = container.querySelectorAll('.gsap-item');
|
|
if (items.length === 0) return;
|
|
|
|
// Create GSAP context for this container
|
|
const ctx = gsap.context(() => {
|
|
let timeline;
|
|
|
|
if (animationType === 'punchIn' || animationType === 'punchOut') {
|
|
const targetElements = container.querySelectorAll('.gsap-image');
|
|
|
|
const anim = animationType === 'punchIn'
|
|
? punchIn(targetElements, config)
|
|
: punchOut(targetElements, config);
|
|
|
|
// Create timeline with ScrollTrigger
|
|
timeline = gsap.timeline({
|
|
scrollTrigger: {
|
|
trigger: container,
|
|
start: scrollStart,
|
|
end: scrollEnd,
|
|
scrub: scrub ? 1 : false,
|
|
markers: markers,
|
|
pin: pin,
|
|
toggleActions: scrub ? undefined : 'play reverse play reverse',
|
|
onEnter: () => container.dataset.gsapActive = 'true',
|
|
onLeave: () => container.dataset.gsapActive = 'false',
|
|
onEnterBack: () => container.dataset.gsapActive = 'true',
|
|
onLeaveBack: () => container.dataset.gsapActive = 'false'
|
|
}
|
|
});
|
|
|
|
// Apply animation
|
|
if (anim.from && anim.to) {
|
|
timeline.fromTo(targetElements, anim.from, anim.to);
|
|
} else if (anim.from) {
|
|
timeline.from(targetElements, anim.from);
|
|
} else if (anim.to) {
|
|
timeline.to(targetElements, anim.to);
|
|
}
|
|
} else {
|
|
console.warn('Unsupported animation type. Only zoomIn/zoomOut are available.', config);
|
|
return;
|
|
}
|
|
}, container);
|
|
|
|
// Store context for cleanup
|
|
activeContexts.set(container, ctx);
|
|
|
|
} catch (error) {
|
|
console.error('Error initializing GSAP animation:', error, container);
|
|
}
|
|
});
|
|
|
|
console.log(`Initialized ${activeContexts.size} GSAP scroll animations`);
|
|
}
|
|
|
|
/**
|
|
* Cleanup all active animations
|
|
*/
|
|
function cleanupGsapAnimations() {
|
|
activeContexts.forEach((ctx, container) => {
|
|
ctx.revert(); // Reverts all GSAP changes made in this context
|
|
container.removeAttribute('data-gsap-active');
|
|
});
|
|
activeContexts.clear();
|
|
|
|
// Kill all ScrollTriggers
|
|
ScrollTrigger.getAll().forEach(trigger => trigger.kill());
|
|
}
|
|
|
|
/**
|
|
* Refresh ScrollTrigger calculations
|
|
* Useful when content height changes
|
|
*/
|
|
function refreshScrollTrigger() {
|
|
ScrollTrigger.refresh();
|
|
}
|
|
|
|
// Initialize on page load
|
|
document.addEventListener('DOMContentLoaded', initGsapAnimations);
|
|
|
|
// Turbo Drive compatibility
|
|
if (window.Turbo) {
|
|
// Clean up before navigation
|
|
document.addEventListener('turbo:before-render', cleanupGsapAnimations);
|
|
|
|
// Re-initialize after navigation
|
|
document.addEventListener('turbo:render', initGsapAnimations);
|
|
document.addEventListener('turbo:load', () => {
|
|
// Slight delay to ensure DOM is ready
|
|
setTimeout(initGsapAnimations, 100);
|
|
});
|
|
|
|
// Refresh on Turbo frame load
|
|
document.addEventListener('turbo:frame-load', refreshScrollTrigger);
|
|
}
|
|
|
|
// Handle window resize
|
|
let resizeTimeout;
|
|
window.addEventListener('resize', () => {
|
|
clearTimeout(resizeTimeout);
|
|
resizeTimeout = setTimeout(() => {
|
|
refreshScrollTrigger();
|
|
}, 250);
|
|
});
|
|
|
|
// Export for manual control if needed
|
|
export { initGsapAnimations, cleanupGsapAnimations, refreshScrollTrigger };
|