/** * 滚动触发动画系统 JavaScript * 基于 GSAP ScrollTrigger 的动画控制器 * 配合 scroll-animations.css 使用 */ (function() { 'use strict'; // 动画系统配置 const ScrollAnimations = { // 默认配置 config: { // 触发位置(元素进入视口的百分比) triggerStart: 'top 80%', triggerEnd: 'bottom 20%', // 是否启用标记(调试用) markers: false, // 默认动画持续时间 duration: 0.8, // 默认缓动函数 ease: 'power2.out', // 是否启用批量处理 batch: true, // 批量处理间隔 batchInterval: 0.1, // 是否在元素离开视口时反转动画 toggleActions: 'play none none reverse' }, // 动画类型映射 animationTypes: { 'fade-in-up': { from: { opacity: 0, y: 50 }, to: { opacity: 1, y: 0 } }, 'fade-in-down': { from: { opacity: 0, y: -50 }, to: { opacity: 1, y: 0 } }, 'fade-in-left': { from: { opacity: 0, x: -50 }, to: { opacity: 1, x: 0 } }, 'fade-in-right': { from: { opacity: 0, x: 50 }, to: { opacity: 1, x: 0 } }, 'scale-in': { from: { opacity: 0, scale: 0.8 }, to: { opacity: 1, scale: 1 } }, 'rotate-in': { from: { opacity: 0, rotation: -10, scale: 0.9 }, to: { opacity: 1, rotation: 0, scale: 1 } }, 'fade-scale-up': { from: { opacity: 0, y: 30, scale: 0.95 }, to: { opacity: 1, y: 0, scale: 1 } }, 'blur-in': { from: { opacity: 0, filter: 'blur(10px)', y: 20 }, to: { opacity: 1, filter: 'blur(0px)', y: 0 } } }, // 初始化函数 init: function() { // 等待 GSAP 和 ScrollTrigger 加载完成 this.waitForGSAP(() => { this.setupScrollTrigger(); this.initAnimations(); this.setupStaggerAnimations(); this.setupTextReveal(); this.bindEvents(); }); }, // 等待 GSAP 加载 waitForGSAP: function(callback) { if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') { callback(); } else { // 监听 GSAP 准备就绪事件 document.addEventListener('gsapReady', callback); // 备用方案:轮询检查 let attempts = 0; const checkGSAP = () => { attempts++; if (typeof gsap !== 'undefined' && typeof ScrollTrigger !== 'undefined') { callback(); } else if (attempts < 50) { setTimeout(checkGSAP, 100); } else { this.initCSSFallback(); } }; setTimeout(checkGSAP, 100); } }, // 设置 ScrollTrigger setupScrollTrigger: function() { gsap.registerPlugin(ScrollTrigger); // 设置全局配置 ScrollTrigger.config({ autoRefreshEvents: "visibilitychange,DOMContentLoaded,load" }); // 启用批量处理以提高性能 ScrollTrigger.batch(".scroll-animate", { onEnter: (elements) => { elements.forEach(element => { this.animateElement(element, 'enter'); }); }, onLeave: (elements) => { if (this.config.toggleActions.includes('reverse')) { elements.forEach(element => { this.animateElement(element, 'leave'); }); } }, start: this.config.triggerStart, end: this.config.triggerEnd }); }, // 初始化动画 initAnimations: function() { // 为每种动画类型创建 ScrollTrigger Object.keys(this.animationTypes).forEach(animationType => { const elements = document.querySelectorAll(`.${animationType}`); if (elements.length > 0) { this.createScrollTrigger(elements, animationType); } }); }, // 创建 ScrollTrigger createScrollTrigger: function(elements, animationType) { const animationConfig = this.animationTypes[animationType]; elements.forEach((element, index) => { // 设置初始状态 gsap.set(element, animationConfig.from); // 获取延迟时间 const delay = this.getElementDelay(element, index); // 创建动画 const animation = gsap.to(element, { ...animationConfig.to, duration: this.config.duration, ease: this.config.ease, delay: delay, scrollTrigger: { trigger: element, start: this.config.triggerStart, end: this.config.triggerEnd, toggleActions: this.config.toggleActions, markers: this.config.markers, onEnter: () => { element.classList.add('animate-active'); this.dispatchEvent(element, 'scrollAnimateEnter'); }, onLeave: () => { if (this.config.toggleActions.includes('reverse')) { element.classList.remove('animate-active'); this.dispatchEvent(element, 'scrollAnimateLeave'); } } } }); // 存储动画实例到元素上 element._scrollAnimation = animation; }); }, // 设置交错动画 setupStaggerAnimations: function() { const staggerContainers = document.querySelectorAll('.stagger-container'); staggerContainers.forEach(container => { const items = container.querySelectorAll('.stagger-item'); if (items.length > 0) { // 设置初始状态 gsap.set(items, { opacity: 0, y: 30 }); // 创建交错动画 gsap.to(items, { opacity: 1, y: 0, duration: 0.6, ease: this.config.ease, stagger: this.config.batchInterval, scrollTrigger: { trigger: container, start: this.config.triggerStart, end: this.config.triggerEnd, toggleActions: this.config.toggleActions, onEnter: () => { container.classList.add('animate-active'); } } }); } }); }, // 设置文字揭示动画 setupTextReveal: function() { const textElements = document.querySelectorAll('.text-reveal'); textElements.forEach(element => { // 将文字包装在 span 中 this.wrapTextInSpans(element); const spans = element.querySelectorAll('span'); // 设置初始状态 gsap.set(spans, { opacity: 0, y: '100%' }); // 创建文字动画 gsap.to(spans, { opacity: 1, y: '0%', duration: 0.6, ease: this.config.ease, stagger: 0.05, scrollTrigger: { trigger: element, start: this.config.triggerStart, end: this.config.triggerEnd, toggleActions: this.config.toggleActions } }); }); }, // 包装文字到 span 中 wrapTextInSpans: function(element) { const text = element.textContent; const words = text.split(' '); element.innerHTML = words.map(word => `${word}` ).join(' '); }, // 获取元素延迟时间 getElementDelay: function(element, index) { // 检查是否有延迟类 const delayClasses = [ 'animate-delay-100', 'animate-delay-200', 'animate-delay-300', 'animate-delay-400', 'animate-delay-500', 'animate-delay-600', 'animate-delay-700', 'animate-delay-800' ]; for (let delayClass of delayClasses) { if (element.classList.contains(delayClass)) { return parseInt(delayClass.split('-')[2]) / 1000; } } // 如果启用批量处理,返回基于索引的延迟 return this.config.batch ? index * this.config.batchInterval : 0; }, // 动画元素 animateElement: function(element, direction) { if (direction === 'enter') { element.classList.add('animate-active'); } else { element.classList.remove('animate-active'); } }, // 分发自定义事件 dispatchEvent: function(element, eventName) { const event = new CustomEvent(eventName, { detail: { element: element }, bubbles: true }); element.dispatchEvent(event); }, // CSS 回退方案 initCSSFallback: function() { // 使用 Intersection Observer 作为回退 if ('IntersectionObserver' in window) { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animate-active'); } }); }, { threshold: 0.1, rootMargin: '0px 0px -20% 0px' }); // 观察所有动画元素 Object.keys(this.animationTypes).forEach(animationType => { const elements = document.querySelectorAll(`.${animationType}`); elements.forEach(element => observer.observe(element)); }); } }, // 绑定事件 bindEvents: function() { // 窗口大小改变时刷新 ScrollTrigger window.addEventListener('resize', () => { if (typeof ScrollTrigger !== 'undefined') { ScrollTrigger.refresh(); } }); // 页面可见性改变时暂停/恢复动画 document.addEventListener('visibilitychange', () => { if (typeof gsap !== 'undefined') { if (document.hidden) { gsap.globalTimeline.pause(); } else { gsap.globalTimeline.resume(); } } }); }, // 公共 API api: { // 刷新所有 ScrollTrigger refresh: function() { if (typeof ScrollTrigger !== 'undefined') { ScrollTrigger.refresh(); } }, // 启用调试模式 enableDebug: function() { ScrollAnimations.config.markers = true; if (typeof ScrollTrigger !== 'undefined') { ScrollTrigger.refresh(); } }, // 禁用调试模式 disableDebug: function() { ScrollAnimations.config.markers = false; if (typeof ScrollTrigger !== 'undefined') { ScrollTrigger.refresh(); } }, // 手动触发元素动画 triggerAnimation: function(element) { if (element && element._scrollAnimation) { element._scrollAnimation.play(); } } } }; // 等待 DOM 加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { ScrollAnimations.init(); }); } else { ScrollAnimations.init(); } // 将 API 暴露到全局 window.ScrollAnimations = ScrollAnimations.api; })();