1
This commit is contained in:
349
src/static/animation_helper.js
Normal file
349
src/static/animation_helper.js
Normal file
@@ -0,0 +1,349 @@
|
||||
/**
|
||||
* 动画助手 - 处理滚动触发动画和交互动画
|
||||
*/
|
||||
|
||||
class AnimationHelper {
|
||||
constructor() {
|
||||
this.animatedElements = [];
|
||||
this.isInitialized = false;
|
||||
this.observer = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化动画助手
|
||||
*/
|
||||
init() {
|
||||
if (this.isInitialized) return;
|
||||
|
||||
// 设置Intersection Observer
|
||||
this.setupIntersectionObserver();
|
||||
|
||||
// 添加滚动事件监听
|
||||
this.addScrollListeners();
|
||||
|
||||
// 初始化页面加载动画
|
||||
this.initPageLoadAnimations();
|
||||
|
||||
this.isInitialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Intersection Observer用于滚动触发动画
|
||||
*/
|
||||
setupIntersectionObserver() {
|
||||
if (!window.IntersectionObserver) {
|
||||
// 降级处理:直接显示所有元素
|
||||
this.showAllElements();
|
||||
return;
|
||||
}
|
||||
|
||||
this.observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
this.animateElement(entry.target);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -50px 0px'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加滚动事件监听
|
||||
*/
|
||||
addScrollListeners() {
|
||||
// 监听滚动事件,用于视差效果
|
||||
window.addEventListener('scroll', this.handleScroll.bind(this), { passive: true });
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', this.handleResize.bind(this), { passive: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理滚动事件
|
||||
*/
|
||||
handleScroll() {
|
||||
this.updateParallaxEffects();
|
||||
this.updateScrollProgress();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理窗口大小变化
|
||||
*/
|
||||
handleResize() {
|
||||
// 重新计算动画元素位置
|
||||
this.recalculateAnimations();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化页面加载动画
|
||||
*/
|
||||
initPageLoadAnimations() {
|
||||
// 为页面加载动画添加延迟
|
||||
setTimeout(() => {
|
||||
this.animatePageLoad();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面加载动画
|
||||
*/
|
||||
animatePageLoad() {
|
||||
// 标题动画
|
||||
const titles = document.querySelectorAll('.section-title.animate-on-scroll');
|
||||
titles.forEach((title, index) => {
|
||||
setTimeout(() => {
|
||||
title.classList.add('visible');
|
||||
}, index * 200);
|
||||
});
|
||||
|
||||
// 内容动画
|
||||
const contentElements = document.querySelectorAll('.animate-stagger');
|
||||
contentElements.forEach((container, containerIndex) => {
|
||||
const children = container.children;
|
||||
Array.from(children).forEach((child, childIndex) => {
|
||||
setTimeout(() => {
|
||||
child.style.opacity = '1';
|
||||
child.style.transform = 'translateY(0)';
|
||||
}, (containerIndex * 300) + (childIndex * 100));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 为元素添加滚动触发动画
|
||||
*/
|
||||
addScrollAnimation(element, options = {}) {
|
||||
if (!element) return;
|
||||
|
||||
const defaultOptions = {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -50px 0px',
|
||||
animationClass: 'animate-in',
|
||||
delay: 0
|
||||
};
|
||||
|
||||
const config = { ...defaultOptions, ...options };
|
||||
|
||||
// 添加动画类
|
||||
element.classList.add('scroll-animate');
|
||||
|
||||
// 设置延迟
|
||||
if (config.delay > 0) {
|
||||
element.style.transitionDelay = `${config.delay}s`;
|
||||
}
|
||||
|
||||
// 添加到观察列表
|
||||
if (this.observer) {
|
||||
this.observer.observe(element);
|
||||
}
|
||||
|
||||
this.animatedElements.push(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为多个元素添加交错动画
|
||||
*/
|
||||
addStaggerAnimation(container, options = {}) {
|
||||
if (!container) return;
|
||||
|
||||
const defaultOptions = {
|
||||
delay: 0.1,
|
||||
staggerDelay: 0.1,
|
||||
animationClass: 'visible'
|
||||
};
|
||||
|
||||
const config = { ...defaultOptions, ...options };
|
||||
|
||||
// 添加交错动画类
|
||||
container.classList.add('animate-stagger');
|
||||
|
||||
// 为子元素设置初始状态
|
||||
const children = Array.from(container.children);
|
||||
children.forEach((child, index) => {
|
||||
child.style.opacity = '0';
|
||||
child.style.transform = 'translateY(30px)';
|
||||
child.style.transitionDelay = `${config.delay + (index * config.staggerDelay)}s`;
|
||||
});
|
||||
|
||||
// 添加到观察列表
|
||||
if (this.observer) {
|
||||
this.observer.observe(container);
|
||||
}
|
||||
|
||||
this.animatedElements.push(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发元素动画
|
||||
*/
|
||||
animateElement(element) {
|
||||
if (!element) return;
|
||||
|
||||
// 添加可见类
|
||||
if (element.classList.contains('scroll-animate')) {
|
||||
element.classList.add('animate-in');
|
||||
} else if (element.classList.contains('animate-stagger')) {
|
||||
element.classList.add('visible');
|
||||
}
|
||||
|
||||
// 触发自定义事件
|
||||
element.dispatchEvent(new CustomEvent('animationStart', {
|
||||
detail: { element, type: 'scroll' }
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发动画
|
||||
*/
|
||||
triggerAnimation(element, animationClass = 'animate-in') {
|
||||
if (!element) return;
|
||||
|
||||
element.classList.add(animationClass);
|
||||
|
||||
// 触发自定义事件
|
||||
element.dispatchEvent(new CustomEvent('animationTrigger', {
|
||||
detail: { element, animationClass }
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置元素动画状态
|
||||
*/
|
||||
resetAnimation(element) {
|
||||
if (!element) return;
|
||||
|
||||
element.classList.remove('animate-in', 'visible');
|
||||
element.style.transitionDelay = '';
|
||||
|
||||
// 重置样式
|
||||
if (element.classList.contains('scroll-animate')) {
|
||||
element.style.opacity = '0';
|
||||
element.style.transform = 'translateY(50px)';
|
||||
} else if (element.classList.contains('animate-stagger')) {
|
||||
const children = Array.from(element.children);
|
||||
children.forEach(child => {
|
||||
child.style.opacity = '0';
|
||||
child.style.transform = 'translateY(30px)';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新视差效果
|
||||
*/
|
||||
updateParallaxEffects() {
|
||||
const parallaxElements = document.querySelectorAll('[data-parallax]');
|
||||
const scrolled = window.pageYOffset;
|
||||
|
||||
parallaxElements.forEach(element => {
|
||||
const speed = parseFloat(element.dataset.parallax) || 0.5;
|
||||
const yPos = -(scrolled * speed);
|
||||
element.style.transform = `translateY(${yPos}px)`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新滚动进度
|
||||
*/
|
||||
updateScrollProgress() {
|
||||
const progressElements = document.querySelectorAll('[data-scroll-progress]');
|
||||
const scrolled = window.pageYOffset;
|
||||
const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
|
||||
const progress = (scrolled / maxScroll) * 100;
|
||||
|
||||
progressElements.forEach(element => {
|
||||
if (element.dataset.scrollProgress === 'bar') {
|
||||
element.style.width = `${progress}%`;
|
||||
} else if (element.dataset.scrollProgress === 'number') {
|
||||
element.textContent = Math.round(progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新计算动画
|
||||
*/
|
||||
recalculateAnimations() {
|
||||
// 重新观察所有元素
|
||||
this.animatedElements.forEach(element => {
|
||||
if (this.observer) {
|
||||
this.observer.unobserve(element);
|
||||
this.observer.observe(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示所有元素(降级处理)
|
||||
*/
|
||||
showAllElements() {
|
||||
const scrollElements = document.querySelectorAll('.scroll-animate, .animate-stagger');
|
||||
scrollElements.forEach(element => {
|
||||
if (element.classList.contains('scroll-animate')) {
|
||||
element.classList.add('animate-in');
|
||||
} else if (element.classList.contains('animate-stagger')) {
|
||||
element.classList.add('visible');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加鼠标悬停动画
|
||||
*/
|
||||
addHoverAnimation(element, animationType = 'scale') {
|
||||
if (!element) return;
|
||||
|
||||
const animations = {
|
||||
scale: 'hover-scale',
|
||||
lift: 'hover-lift',
|
||||
rotate: 'hover-rotate',
|
||||
glow: 'hover-glow',
|
||||
bounce: 'hover-bounce'
|
||||
};
|
||||
|
||||
const animationClass = animations[animationType] || 'hover-scale';
|
||||
element.classList.add(animationClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加点击动画
|
||||
*/
|
||||
addClickAnimation(element, animationType = 'pulse') {
|
||||
if (!element) return;
|
||||
|
||||
element.addEventListener('click', () => {
|
||||
element.classList.add(`animate ${animationType}`);
|
||||
|
||||
// 动画结束后移除类
|
||||
setTimeout(() => {
|
||||
element.classList.remove(`animate ${animationType}`);
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁动画助手
|
||||
*/
|
||||
destroy() {
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
|
||||
window.removeEventListener('scroll', this.handleScroll);
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
|
||||
this.animatedElements = [];
|
||||
this.isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建全局实例
|
||||
window.animationHelper = new AnimationHelper();
|
||||
|
||||
// 导出类(如果使用模块系统)
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = AnimationHelper;
|
||||
}
|
||||
Reference in New Issue
Block a user