// 动画管理器 - 微信小程序专用 // 提供统一的动画效果和页面过渡管理 /** * 动画管理器 * 功能: * 1. 页面过渡动画 * 2. 组件动画效果 * 3. 交互反馈动画 * 4. 加载状态动画 * 5. 手势动画 * 6. 性能优化动画 */ class AnimationManager { constructor() { this.isInitialized = false; // 动画配置 this.config = { // 动画开关 enabled: true, // 性能模式 performanceMode: 'auto', // auto, high, low // 动画时长配置 durations: { fast: 200, normal: 300, slow: 500, page: 400 }, // 缓动函数配置 easings: { ease: 'ease', easeIn: 'ease-in', easeOut: 'ease-out', easeInOut: 'ease-in-out', linear: 'linear', spring: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', bounce: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)' }, // 页面过渡配置 pageTransitions: { slideLeft: { enter: { transform: 'translateX(100%)', opacity: 0 }, active: { transform: 'translateX(0)', opacity: 1 }, leave: { transform: 'translateX(-100%)', opacity: 0 } }, slideRight: { enter: { transform: 'translateX(-100%)', opacity: 0 }, active: { transform: 'translateX(0)', opacity: 1 }, leave: { transform: 'translateX(100%)', opacity: 0 } }, slideUp: { enter: { transform: 'translateY(100%)', opacity: 0 }, active: { transform: 'translateY(0)', opacity: 1 }, leave: { transform: 'translateY(-100%)', opacity: 0 } }, slideDown: { enter: { transform: 'translateY(-100%)', opacity: 0 }, active: { transform: 'translateY(0)', opacity: 1 }, leave: { transform: 'translateY(100%)', opacity: 0 } }, fade: { enter: { opacity: 0 }, active: { opacity: 1 }, leave: { opacity: 0 } }, scale: { enter: { transform: 'scale(0.8)', opacity: 0 }, active: { transform: 'scale(1)', opacity: 1 }, leave: { transform: 'scale(1.2)', opacity: 0 } } } }; // 动画实例缓存 this.animationCache = new Map(); // 性能监控 this.performanceStats = { animationCount: 0, averageDuration: 0, droppedFrames: 0 }; this.init(); } // 初始化动画管理器 init() { if (this.isInitialized) return; try { // 检测设备性能 this.detectPerformance(); // 设置全局动画样式 this.setupGlobalStyles(); this.isInitialized = true; } catch (error) { console.error('❌ 动画管理器初始化失败:', error); } } // 🎭 ===== 基础动画 ===== // 创建动画实例 createAnimation(options = {}) { const defaultOptions = { duration: this.config.durations.normal, timingFunction: this.config.easings.easeOut, delay: 0, transformOrigin: '50% 50% 0' }; const animationOptions = { ...defaultOptions, ...options }; // 根据性能模式调整动画 if (this.config.performanceMode === 'low') { animationOptions.duration = Math.min(animationOptions.duration, 200); } const animation = wx.createAnimation(animationOptions); // 缓存动画实例 const animationId = this.generateAnimationId(); this.animationCache.set(animationId, { animation: animation, options: animationOptions, createTime: Date.now() }); return { animation, animationId }; } // 淡入动画 fadeIn(options = {}) { const { animation } = this.createAnimation({ duration: this.config.durations.normal, timingFunction: this.config.easings.easeOut, ...options }); return animation.opacity(1).step(); } // 淡出动画 fadeOut(options = {}) { const { animation } = this.createAnimation({ duration: this.config.durations.normal, timingFunction: this.config.easings.easeIn, ...options }); return animation.opacity(0).step(); } // 滑入动画 slideIn(direction = 'left', options = {}) { const { animation } = this.createAnimation({ duration: this.config.durations.normal, timingFunction: this.config.easings.spring, ...options }); const transforms = { left: () => animation.translateX(0), right: () => animation.translateX(0), up: () => animation.translateY(0), down: () => animation.translateY(0) }; return transforms[direction]().opacity(1).step(); } // 滑出动画 slideOut(direction = 'left', distance = '100%', options = {}) { const { animation } = this.createAnimation({ duration: this.config.durations.normal, timingFunction: this.config.easings.easeIn, ...options }); const transforms = { left: () => animation.translateX(`-${distance}`), right: () => animation.translateX(distance), up: () => animation.translateY(`-${distance}`), down: () => animation.translateY(distance) }; return transforms[direction]().opacity(0).step(); } // 缩放动画 scale(scale = 1, options = {}) { const { animation } = this.createAnimation({ duration: this.config.durations.normal, timingFunction: this.config.easings.spring, ...options }); return animation.scale(scale).step(); } // 旋转动画 rotate(angle = 360, options = {}) { const { animation } = this.createAnimation({ duration: this.config.durations.slow, timingFunction: this.config.easings.linear, ...options }); return animation.rotate(angle).step(); } // 🎪 ===== 组合动画 ===== // 弹跳进入动画 bounceIn(options = {}) { const { animation } = this.createAnimation({ duration: this.config.durations.slow, timingFunction: this.config.easings.bounce, ...options }); return animation .scale(0.3).opacity(0).step({ duration: 0 }) .scale(1.05).opacity(1).step({ duration: 200 }) .scale(0.95).step({ duration: 100 }) .scale(1).step({ duration: 100 }); } // 弹跳退出动画 bounceOut(options = {}) { const { animation } = this.createAnimation({ duration: this.config.durations.normal, timingFunction: this.config.easings.easeIn, ...options }); return animation .scale(1.1).step({ duration: 100 }) .scale(0).opacity(0).step({ duration: 200 }); } // 摇摆动画 shake(options = {}) { const { animation } = this.createAnimation({ duration: this.config.durations.fast, timingFunction: this.config.easings.linear, ...options }); return animation .translateX(-10).step({ duration: 50 }) .translateX(10).step({ duration: 50 }) .translateX(-10).step({ duration: 50 }) .translateX(10).step({ duration: 50 }) .translateX(0).step({ duration: 50 }); } // 脉冲动画 pulse(options = {}) { const { animation } = this.createAnimation({ duration: this.config.durations.normal, timingFunction: this.config.easings.easeInOut, ...options }); return animation .scale(1).step({ duration: 0 }) .scale(1.05).step({ duration: 150 }) .scale(1).step({ duration: 150 }); } // 🎬 ===== 页面过渡 ===== // 页面进入动画 pageEnter(transitionType = 'slideLeft', options = {}) { const transition = this.config.pageTransitions[transitionType]; if (!transition) { console.warn('未知的页面过渡类型:', transitionType); return this.fadeIn(options); } const { animation } = this.createAnimation({ duration: this.config.durations.page, timingFunction: this.config.easings.easeOut, ...options }); // 设置初始状态 this.applyTransform(animation, transition.enter); animation.step({ duration: 0 }); // 执行进入动画 this.applyTransform(animation, transition.active); return animation.step(); } // 页面退出动画 pageLeave(transitionType = 'slideLeft', options = {}) { const transition = this.config.pageTransitions[transitionType]; if (!transition) { console.warn('未知的页面过渡类型:', transitionType); return this.fadeOut(options); } const { animation } = this.createAnimation({ duration: this.config.durations.page, timingFunction: this.config.easings.easeIn, ...options }); // 执行退出动画 this.applyTransform(animation, transition.leave); return animation.step(); } // 🎯 ===== 交互动画 ===== // 按钮点击动画 buttonPress(options = {}) { const { animation } = this.createAnimation({ duration: this.config.durations.fast, timingFunction: this.config.easings.easeOut, ...options }); return animation .scale(0.95).step({ duration: 100 }) .scale(1).step({ duration: 100 }); } // 卡片悬停动画 cardHover(options = {}) { const { animation } = this.createAnimation({ duration: this.config.durations.normal, timingFunction: this.config.easings.easeOut, ...options }); return animation .scale(1.02) .translateY(-2) .step(); } // 列表项滑动动画 listItemSlide(direction = 'left', options = {}) { const { animation } = this.createAnimation({ duration: this.config.durations.normal, timingFunction: this.config.easings.easeOut, ...options }); const distance = direction === 'left' ? -80 : 80; return animation.translateX(distance).step(); } // 🔄 ===== 加载动画 ===== // 旋转加载动画 loadingSpinner(options = {}) { const { animation } = this.createAnimation({ duration: 1000, timingFunction: this.config.easings.linear, ...options }); return animation.rotate(360).step(); } // 脉冲加载动画 loadingPulse(options = {}) { const { animation } = this.createAnimation({ duration: 1000, timingFunction: this.config.easings.easeInOut, ...options }); return animation .opacity(0.3).step({ duration: 500 }) .opacity(1).step({ duration: 500 }); } // 波浪加载动画 loadingWave(delay = 0, options = {}) { const { animation } = this.createAnimation({ duration: 600, timingFunction: this.config.easings.easeInOut, delay: delay, ...options }); return animation .translateY(-10).step({ duration: 300 }) .translateY(0).step({ duration: 300 }); } // 🎨 ===== 工具方法 ===== // 应用变换 applyTransform(animation, transform) { Object.keys(transform).forEach(property => { const value = transform[property]; switch (property) { case 'opacity': animation.opacity(parseFloat(value)); break; case 'transform': // 解析transform值 this.parseTransform(animation, value); break; default: console.warn('不支持的动画属性:', property); } }); } // 解析transform值 parseTransform(animation, transformValue) { const transforms = transformValue.split(' '); transforms.forEach(transform => { if (transform.includes('translateX')) { const value = this.extractValue(transform); animation.translateX(value); } else if (transform.includes('translateY')) { const value = this.extractValue(transform); animation.translateY(value); } else if (transform.includes('scale')) { const value = parseFloat(this.extractValue(transform)); animation.scale(value); } else if (transform.includes('rotate')) { const value = this.extractValue(transform); animation.rotate(parseFloat(value)); } }); } // 提取变换值 extractValue(transform) { const match = transform.match(/\(([^)]+)\)/); return match ? match[1] : '0'; } // 检测设备性能 detectPerformance() { try { // 使用新的API替代已弃用的wx.getSystemInfoSync const deviceInfo = wx.getDeviceInfo(); const { platform, system, model } = deviceInfo; // 简单的性能评估 let performanceScore = 100; // 根据平台调整 if (platform === 'android') { performanceScore -= 10; } // 根据系统版本调整 const systemVersion = parseFloat(system.match(/[\d.]+/)?.[0] || '0'); if (systemVersion < 10) { performanceScore -= 20; } // 根据机型调整(简化判断) if (model && model.toLowerCase().includes('redmi')) { performanceScore -= 15; } // 设置性能模式 if (performanceScore >= 80) { this.config.performanceMode = 'high'; } else if (performanceScore >= 60) { this.config.performanceMode = 'auto'; } else { this.config.performanceMode = 'low'; // 低性能模式下禁用复杂动画 this.config.durations.normal = 200; this.config.durations.slow = 300; } } catch (error) { console.error('❌ 性能检测失败:', error); this.config.performanceMode = 'auto'; } } // 设置全局动画样式 setupGlobalStyles() { // 这里可以设置一些全局的CSS动画类 // 微信小程序中主要通过WXSS实现 } // 生成动画ID generateAnimationId() { return `anim_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } // 清理动画缓存 cleanupAnimationCache() { const now = Date.now(); const expireTime = 5 * 60 * 1000; // 5分钟 for (const [id, cache] of this.animationCache) { if (now - cache.createTime > expireTime) { this.animationCache.delete(id); } } } // 获取性能统计 getPerformanceStats() { return { ...this.performanceStats, cacheSize: this.animationCache.size, performanceMode: this.config.performanceMode }; } // 设置动画开关 setEnabled(enabled) { this.config.enabled = enabled; } // 设置性能模式 setPerformanceMode(mode) { if (['auto', 'high', 'low'].includes(mode)) { this.config.performanceMode = mode; } } // 销毁动画管理器 destroy() { this.animationCache.clear(); this.isInitialized = false; } } // 创建全局实例 const animationManager = new AnimationManager(); module.exports = animationManager;