From 1ad48b234c439caa136080679182f7e27a3326f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Mon, 25 Aug 2025 15:28:08 +0800 Subject: [PATCH] 1 --- src/main.js | 1 + src/static/animation_helper.js | 349 +++++++++++++++++++++++++++++++++ src/views/index.vue | 61 +++--- 3 files changed, 382 insertions(+), 29 deletions(-) create mode 100644 src/static/animation_helper.js diff --git a/src/main.js b/src/main.js index 05933c6..02721a0 100644 --- a/src/main.js +++ b/src/main.js @@ -3,6 +3,7 @@ import './assets/css/common.css' import './assets/css/animations.css' import 'swiper/css/swiper.min.css' import 'es6-promise/auto' +import './static/animation_helper' import Vue from 'vue' import App from './App.vue' import Vuex from 'vuex' diff --git a/src/static/animation_helper.js b/src/static/animation_helper.js new file mode 100644 index 0000000..5e4aa73 --- /dev/null +++ b/src/static/animation_helper.js @@ -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; +} diff --git a/src/views/index.vue b/src/views/index.vue index 99bf828..851d4fe 100644 --- a/src/views/index.vue +++ b/src/views/index.vue @@ -1,9 +1,12 @@