miniprogramme/utils/animation-manager.js
2025-09-12 16:08:17 +08:00

564 lines
15 KiB
JavaScript

// 动画管理器 - 微信小程序专用
// 提供统一的动画效果和页面过渡管理
/**
* 动画管理器
* 功能:
* 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;
console.log('🎭 初始化动画管理器...');
try {
// 检测设备性能
this.detectPerformance();
// 设置全局动画样式
this.setupGlobalStyles();
this.isInitialized = true;
console.log('✅ 动画管理器初始化完成');
} 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;
}
console.log('🎭 设备性能评估:', {
score: performanceScore,
mode: this.config.performanceMode
});
} 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;
console.log('🎭 动画开关:', enabled ? '开启' : '关闭');
}
// 设置性能模式
setPerformanceMode(mode) {
if (['auto', 'high', 'low'].includes(mode)) {
this.config.performanceMode = mode;
console.log('🎭 性能模式:', mode);
}
}
// 销毁动画管理器
destroy() {
this.animationCache.clear();
this.isInitialized = false;
console.log('🎭 动画管理器已销毁');
}
}
// 创建全局实例
const animationManager = new AnimationManager();
module.exports = animationManager;