完成v1.1.0
This commit is contained in:
465
website/public/js/animations.js
Normal file
465
website/public/js/animations.js
Normal file
@@ -0,0 +1,465 @@
|
||||
/**
|
||||
* 动画和过渡效果管理
|
||||
*/
|
||||
|
||||
/**
|
||||
* 初始化所有动画
|
||||
*/
|
||||
function initAnimations() {
|
||||
console.log('初始化动画系统...');
|
||||
|
||||
// 初始化滚动动画
|
||||
initScrollAnimations();
|
||||
|
||||
// 初始化视差效果
|
||||
initParallaxEffects();
|
||||
|
||||
// 初始化计数器动画
|
||||
initCounterAnimations();
|
||||
|
||||
// 初始化进度条动画
|
||||
initProgressBars();
|
||||
|
||||
// 初始化打字机效果
|
||||
initTypewriterEffects();
|
||||
|
||||
// 初始化骨架屏
|
||||
initSkeletonScreens();
|
||||
|
||||
console.log('动画系统初始化完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化滚动动画
|
||||
*/
|
||||
function initScrollAnimations() {
|
||||
const animatedElements = document.querySelectorAll('.animate-on-scroll');
|
||||
|
||||
if ('IntersectionObserver' in window) {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('animated');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -50px 0px'
|
||||
});
|
||||
|
||||
animatedElements.forEach(element => observer.observe(element));
|
||||
} else {
|
||||
// 回退方案:直接添加动画类
|
||||
animatedElements.forEach(element => {
|
||||
element.classList.add('animated');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视差效果
|
||||
*/
|
||||
function initParallaxEffects() {
|
||||
const parallaxElements = document.querySelectorAll('.parallax');
|
||||
|
||||
if (parallaxElements.length === 0) return;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrolled = window.pageYOffset;
|
||||
|
||||
parallaxElements.forEach(element => {
|
||||
const speed = parseFloat(element.dataset.speed) || 0.5;
|
||||
const yPos = -(scrolled * speed);
|
||||
element.style.transform = `translateY(${yPos}px)`;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化计数器动画
|
||||
*/
|
||||
function initCounterAnimations() {
|
||||
const counters = document.querySelectorAll('.counter');
|
||||
|
||||
if (counters.length === 0) return;
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const counter = entry.target;
|
||||
const target = parseInt(counter.dataset.target) || 0;
|
||||
const duration = parseInt(counter.dataset.duration) || 2000;
|
||||
const increment = target / (duration / 16); // 60fps
|
||||
let current = 0;
|
||||
|
||||
const updateCounter = () => {
|
||||
current += increment;
|
||||
if (current < target) {
|
||||
counter.textContent = Math.floor(current).toLocaleString();
|
||||
requestAnimationFrame(updateCounter);
|
||||
} else {
|
||||
counter.textContent = target.toLocaleString();
|
||||
}
|
||||
};
|
||||
|
||||
updateCounter();
|
||||
observer.unobserve(counter);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
counters.forEach(counter => observer.observe(counter));
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化进度条动画
|
||||
*/
|
||||
function initProgressBars() {
|
||||
const progressBars = document.querySelectorAll('.progress-bar');
|
||||
|
||||
if (progressBars.length === 0) return;
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const progressBar = entry.target;
|
||||
const width = progressBar.dataset.width || '100%';
|
||||
const duration = parseInt(progressBar.dataset.duration) || 1000;
|
||||
|
||||
progressBar.style.transition = `width ${duration}ms ease`;
|
||||
progressBar.style.width = width;
|
||||
|
||||
observer.unobserve(progressBar);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
progressBars.forEach(bar => observer.observe(bar));
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化打字机效果
|
||||
*/
|
||||
function initTypewriterEffects() {
|
||||
const typewriters = document.querySelectorAll('.typewriter');
|
||||
|
||||
typewriters.forEach(element => {
|
||||
const text = element.textContent;
|
||||
element.textContent = '';
|
||||
element.style.width = '0';
|
||||
|
||||
let i = 0;
|
||||
const type = () => {
|
||||
if (i < text.length) {
|
||||
element.textContent += text.charAt(i);
|
||||
element.style.width = `${(i + 1) / text.length * 100}%`;
|
||||
i++;
|
||||
setTimeout(type, 100);
|
||||
}
|
||||
};
|
||||
|
||||
// 延迟开始打字效果
|
||||
setTimeout(type, 500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化骨架屏
|
||||
*/
|
||||
function initSkeletonScreens() {
|
||||
const skeletonElements = document.querySelectorAll('.skeleton');
|
||||
|
||||
if (skeletonElements.length === 0) return;
|
||||
|
||||
// 模拟内容加载
|
||||
setTimeout(() => {
|
||||
skeletonElements.forEach(element => {
|
||||
element.classList.remove('skeleton');
|
||||
element.classList.add('fade-in-load');
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建骨架屏占位符
|
||||
* @param {HTMLElement} container - 容器元素
|
||||
* @param {number} count - 骨架屏数量
|
||||
* @param {string} type - 骨架屏类型: 'text', 'card', 'image'
|
||||
*/
|
||||
function createSkeletonPlaceholders(container, count = 3, type = 'card') {
|
||||
const skeletonClasses = {
|
||||
'text': 'skeleton skeleton-text',
|
||||
'card': 'skeleton skeleton-card',
|
||||
'image': 'skeleton skeleton-image'
|
||||
};
|
||||
|
||||
const skeletonClass = skeletonClasses[type] || skeletonClasses.card;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const skeleton = document.createElement('div');
|
||||
skeleton.className = skeletonClass;
|
||||
container.appendChild(skeleton);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示页面切换动画
|
||||
* @param {string} direction - 切换方向: 'left', 'right', 'up', 'down'
|
||||
*/
|
||||
function showPageTransition(direction = 'right') {
|
||||
const transition = document.getElementById('page-transition');
|
||||
if (!transition) return;
|
||||
|
||||
const directions = {
|
||||
'left': 'slideInLeft',
|
||||
'right': 'slideInRight',
|
||||
'up': 'slideInUp',
|
||||
'down': 'slideInDown'
|
||||
};
|
||||
|
||||
const animation = directions[direction] || 'slideInRight';
|
||||
transition.className = `page-transition animate-${animation}`;
|
||||
transition.classList.add('active');
|
||||
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
transition.classList.remove('active');
|
||||
setTimeout(resolve, 300);
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示元素动画
|
||||
* @param {HTMLElement} element - 要动画的元素
|
||||
* @param {string} animation - 动画名称
|
||||
* @param {number} duration - 动画持续时间(毫秒)
|
||||
*/
|
||||
function animateElement(element, animation = 'fadeIn', duration = 300) {
|
||||
if (!element) return;
|
||||
|
||||
element.style.animation = `${animation} ${duration}ms ease`;
|
||||
element.classList.add('animated');
|
||||
|
||||
// 动画完成后移除动画类
|
||||
setTimeout(() => {
|
||||
element.style.animation = '';
|
||||
}, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建波纹效果
|
||||
* @param {Event} event - 点击事件
|
||||
* @param {string} color - 波纹颜色
|
||||
*/
|
||||
function createRippleEffect(event, color = 'rgba(255, 255, 255, 0.7)') {
|
||||
const button = event.currentTarget;
|
||||
const circle = document.createElement('span');
|
||||
const diameter = Math.max(button.clientWidth, button.clientHeight);
|
||||
const radius = diameter / 2;
|
||||
|
||||
circle.style.width = circle.style.height = `${diameter}px`;
|
||||
circle.style.left = `${event.clientX - button.offsetLeft - radius}px`;
|
||||
circle.style.top = `${event.clientY - button.offsetTop - radius}px`;
|
||||
circle.style.backgroundColor = color;
|
||||
circle.style.position = 'absolute';
|
||||
circle.style.borderRadius = '50%';
|
||||
circle.style.transform = 'scale(0)';
|
||||
circle.style.animation = 'ripple 600ms linear';
|
||||
|
||||
const ripple = button.querySelector('.ripple');
|
||||
if (ripple) {
|
||||
ripple.remove();
|
||||
}
|
||||
|
||||
button.appendChild(circle);
|
||||
|
||||
// 动画完成后移除波纹元素
|
||||
setTimeout(() => {
|
||||
if (circle.parentNode === button) {
|
||||
button.removeChild(circle);
|
||||
}
|
||||
}, 600);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加波纹效果到按钮
|
||||
* @param {string} selector - 按钮选择器
|
||||
*/
|
||||
function addRippleEffectToButtons(selector = '.btn') {
|
||||
const buttons = document.querySelectorAll(selector);
|
||||
|
||||
buttons.forEach(button => {
|
||||
button.style.position = 'relative';
|
||||
button.style.overflow = 'hidden';
|
||||
|
||||
button.addEventListener('click', function(event) {
|
||||
createRippleEffect(event);
|
||||
});
|
||||
});
|
||||
|
||||
// 添加波纹动画CSS
|
||||
if (!document.querySelector('#ripple-styles')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'ripple-styles';
|
||||
style.textContent = `
|
||||
@keyframes ripple {
|
||||
to {
|
||||
transform: scale(4);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化图片加载动画
|
||||
*/
|
||||
function initImageLoadingAnimations() {
|
||||
const images = document.querySelectorAll('img:not([data-src])');
|
||||
|
||||
images.forEach(img => {
|
||||
if (!img.complete) {
|
||||
img.classList.add('loading');
|
||||
|
||||
img.addEventListener('load', function() {
|
||||
this.classList.remove('loading');
|
||||
this.classList.add('loaded');
|
||||
animateElement(this, 'scaleIn', 500);
|
||||
});
|
||||
|
||||
img.addEventListener('error', function() {
|
||||
this.classList.remove('loading');
|
||||
this.classList.add('error');
|
||||
console.error('图片加载失败:', this.src);
|
||||
});
|
||||
} else {
|
||||
img.classList.add('loaded');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加滚动到顶部按钮
|
||||
*/
|
||||
function initScrollToTopButton() {
|
||||
// 创建按钮
|
||||
const button = document.createElement('button');
|
||||
button.id = 'scroll-to-top';
|
||||
button.className = 'scroll-to-top-btn';
|
||||
button.innerHTML = '<i class="fas fa-chevron-up"></i>';
|
||||
button.title = '回到顶部';
|
||||
button.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
`;
|
||||
|
||||
document.body.appendChild(button);
|
||||
|
||||
// 滚动事件监听
|
||||
window.addEventListener('scroll', () => {
|
||||
if (window.pageYOffset > 300) {
|
||||
button.style.opacity = '1';
|
||||
button.style.visibility = 'visible';
|
||||
button.style.transform = 'translateY(0)';
|
||||
} else {
|
||||
button.style.opacity = '0';
|
||||
button.style.visibility = 'hidden';
|
||||
button.style.transform = 'translateY(20px)';
|
||||
}
|
||||
});
|
||||
|
||||
// 点击事件
|
||||
button.addEventListener('click', () => {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
});
|
||||
|
||||
// 添加悬停效果
|
||||
button.addEventListener('mouseenter', () => {
|
||||
button.style.backgroundColor = 'var(--primary-dark)';
|
||||
button.style.transform = 'scale(1.1)';
|
||||
});
|
||||
|
||||
button.addEventListener('mouseleave', () => {
|
||||
button.style.backgroundColor = 'var(--primary-color)';
|
||||
button.style.transform = 'scale(1)';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能优化:减少重绘
|
||||
*/
|
||||
function optimizeForPerformance() {
|
||||
// 为动画元素添加 will-change
|
||||
const animatedElements = document.querySelectorAll('.animate-on-scroll, .parallax, .counter');
|
||||
animatedElements.forEach(element => {
|
||||
element.style.willChange = 'transform, opacity';
|
||||
});
|
||||
|
||||
// 使用 requestAnimationFrame 优化动画
|
||||
let ticking = false;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(() => {
|
||||
// 执行滚动相关的动画
|
||||
ticking = false;
|
||||
});
|
||||
ticking = true;
|
||||
}
|
||||
});
|
||||
|
||||
// 使用 transform 和 opacity 进行动画(GPU加速)
|
||||
console.log('性能优化已应用:使用GPU加速动画');
|
||||
}
|
||||
|
||||
// DOM加载完成后初始化动画
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initAnimations();
|
||||
addRippleEffectToButtons();
|
||||
initImageLoadingAnimations();
|
||||
initScrollToTopButton();
|
||||
optimizeForPerformance();
|
||||
});
|
||||
} else {
|
||||
initAnimations();
|
||||
addRippleEffectToButtons();
|
||||
initImageLoadingAnimations();
|
||||
initScrollToTopButton();
|
||||
optimizeForPerformance();
|
||||
}
|
||||
|
||||
// 导出函数
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = {
|
||||
initAnimations,
|
||||
showPageTransition,
|
||||
animateElement,
|
||||
createSkeletonPlaceholders,
|
||||
addRippleEffectToButtons
|
||||
};
|
||||
}
|
||||
408
website/public/js/main.js
Normal file
408
website/public/js/main.js
Normal file
@@ -0,0 +1,408 @@
|
||||
// 主JavaScript文件
|
||||
|
||||
/**
|
||||
* 初始化导航栏
|
||||
*/
|
||||
function initNavigation() {
|
||||
const navbarLinks = document.querySelectorAll('.navbar-link');
|
||||
const currentPage = document.body.classList.contains('page-home') ? 'home' :
|
||||
document.body.classList.contains('page-features') ? 'features' :
|
||||
document.body.classList.contains('page-architecture') ? 'architecture' :
|
||||
document.body.classList.contains('page-plugins') ? 'plugins' : 'home';
|
||||
|
||||
navbarLinks.forEach(link => {
|
||||
if (link.getAttribute('data-page') === currentPage) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
|
||||
// 添加点击事件
|
||||
link.addEventListener('click', function(e) {
|
||||
if (this.getAttribute('href') === '#') {
|
||||
e.preventDefault();
|
||||
const page = this.getAttribute('data-page');
|
||||
if (page && page !== currentPage) {
|
||||
navigateToPage(page);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化页面切换
|
||||
*/
|
||||
function initPageTransitions() {
|
||||
const pageTransition = document.getElementById('page-transition');
|
||||
if (!pageTransition) return;
|
||||
|
||||
// 监听页面链接点击
|
||||
document.addEventListener('click', function(e) {
|
||||
const link = e.target.closest('a[data-page]');
|
||||
if (link && link.getAttribute('href') === '#') {
|
||||
e.preventDefault();
|
||||
const page = link.getAttribute('data-page');
|
||||
const currentPage = document.body.className.match(/page-(\w+)/)?.[1];
|
||||
|
||||
if (page && page !== currentPage) {
|
||||
// 显示页面切换动画
|
||||
pageTransition.classList.add('active');
|
||||
|
||||
// 延迟导航
|
||||
setTimeout(() => {
|
||||
navigateToPage(page);
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导航到指定页面
|
||||
* @param {string} page - 页面名称
|
||||
*/
|
||||
function navigateToPage(page) {
|
||||
console.log(`导航到页面: ${page}`);
|
||||
|
||||
// 这里应该使用前端路由或实际页面跳转
|
||||
// 目前先更新URL和页面状态
|
||||
const url = `/${page === 'home' ? '' : page}`;
|
||||
window.history.pushState({ page }, '', url);
|
||||
|
||||
// 更新页面内容
|
||||
updatePageContent(page);
|
||||
|
||||
// 更新导航栏激活状态
|
||||
updateNavigation(page);
|
||||
|
||||
// 隐藏页面切换动画
|
||||
const pageTransition = document.getElementById('page-transition');
|
||||
if (pageTransition) {
|
||||
setTimeout(() => {
|
||||
pageTransition.classList.remove('active');
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新页面内容
|
||||
* @param {string} page - 页面名称
|
||||
*/
|
||||
function updatePageContent(page) {
|
||||
// 更新body类名
|
||||
document.body.className = document.body.className.replace(/page-\w+/, `page-${page}`);
|
||||
|
||||
// 更新页面标题
|
||||
const pageTitles = {
|
||||
'home': 'FutureOSS - 插件化运行时框架',
|
||||
'features': '核心特性 - FutureOSS',
|
||||
'architecture': '系统架构 - FutureOSS',
|
||||
'plugins': '插件生态 - FutureOSS'
|
||||
};
|
||||
|
||||
document.title = pageTitles[page] || pageTitles.home;
|
||||
|
||||
// 这里应该加载新的页面内容
|
||||
// 目前先滚动到顶部
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新导航栏激活状态
|
||||
* @param {string} page - 页面名称
|
||||
*/
|
||||
function updateNavigation(page) {
|
||||
const navbarLinks = document.querySelectorAll('.navbar-link');
|
||||
navbarLinks.forEach(link => {
|
||||
link.classList.remove('active');
|
||||
if (link.getAttribute('data-page') === page) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化按钮交互
|
||||
*/
|
||||
function initButtonInteractions() {
|
||||
// 为所有按钮添加悬停效果
|
||||
const buttons = document.querySelectorAll('.btn');
|
||||
buttons.forEach(button => {
|
||||
button.addEventListener('mouseenter', function() {
|
||||
this.style.transform = 'translateY(-2px)';
|
||||
});
|
||||
|
||||
button.addEventListener('mouseleave', function() {
|
||||
this.style.transform = 'translateY(0)';
|
||||
});
|
||||
});
|
||||
|
||||
// 为卡片添加悬停效果
|
||||
const cards = document.querySelectorAll('.card, .feature-card');
|
||||
cards.forEach(card => {
|
||||
card.addEventListener('mouseenter', function() {
|
||||
this.style.transform = 'translateY(-4px)';
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', function() {
|
||||
this.style.transform = 'translateY(0)';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化滚动效果
|
||||
*/
|
||||
function initScrollEffects() {
|
||||
let lastScrollTop = 0;
|
||||
const navbar = document.querySelector('.navbar');
|
||||
|
||||
if (!navbar) return;
|
||||
|
||||
window.addEventListener('scroll', function() {
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||
|
||||
// 隐藏/显示导航栏
|
||||
if (scrollTop > lastScrollTop && scrollTop > 100) {
|
||||
// 向下滚动,隐藏导航栏
|
||||
navbar.style.transform = 'translateY(-100%)';
|
||||
} else {
|
||||
// 向上滚动,显示导航栏
|
||||
navbar.style.transform = 'translateY(0)';
|
||||
}
|
||||
|
||||
lastScrollTop = scrollTop;
|
||||
|
||||
// 添加滚动阴影
|
||||
if (scrollTop > 10) {
|
||||
navbar.style.boxShadow = 'var(--shadow-md)';
|
||||
} else {
|
||||
navbar.style.boxShadow = 'var(--shadow-sm)';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示消息提示
|
||||
* @param {string} message - 消息内容
|
||||
* @param {string} type - 消息类型: 'success', 'error', 'info', 'warning'
|
||||
*/
|
||||
function showMessage(message, type = 'info') {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `message message-${type}`;
|
||||
messageDiv.textContent = message;
|
||||
messageDiv.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 12px 20px;
|
||||
background-color: ${getMessageColor(type)};
|
||||
color: white;
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 9999;
|
||||
animation: fadeIn 0.3s ease;
|
||||
`;
|
||||
|
||||
document.body.appendChild(messageDiv);
|
||||
|
||||
// 3秒后自动消失
|
||||
setTimeout(() => {
|
||||
messageDiv.style.animation = 'fadeOut 0.3s ease';
|
||||
setTimeout(() => {
|
||||
if (messageDiv.parentNode) {
|
||||
messageDiv.parentNode.removeChild(messageDiv);
|
||||
}
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息颜色
|
||||
* @param {string} type - 消息类型
|
||||
* @returns {string} - 颜色值
|
||||
*/
|
||||
function getMessageColor(type) {
|
||||
const colors = {
|
||||
'success': 'var(--success-color)',
|
||||
'error': 'var(--danger-color)',
|
||||
'info': 'var(--info-color)',
|
||||
'warning': 'var(--warning-color)'
|
||||
};
|
||||
return colors[type] || colors.info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
* @param {Function} func - 要执行的函数
|
||||
* @param {number} wait - 等待时间(毫秒)
|
||||
* @returns {Function} - 防抖后的函数
|
||||
*/
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 节流函数
|
||||
* @param {Function} func - 要执行的函数
|
||||
* @param {number} limit - 限制时间(毫秒)
|
||||
* @returns {Function} - 节流后的函数
|
||||
*/
|
||||
function throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function(...args) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏加载动画
|
||||
*/
|
||||
function hideLoadingAnimation() {
|
||||
const loadingOverlay = document.getElementById('loading-overlay');
|
||||
if (loadingOverlay) {
|
||||
// 简单隐藏
|
||||
loadingOverlay.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示加载动画
|
||||
*/
|
||||
function showLoadingAnimation() {
|
||||
const loadingOverlay = document.getElementById('loading-overlay');
|
||||
if (loadingOverlay) {
|
||||
loadingOverlay.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化页面
|
||||
*/
|
||||
function initPage() {
|
||||
console.log('FutureOSS 网站初始化...');
|
||||
|
||||
// 确保加载动画被隐藏
|
||||
setTimeout(() => {
|
||||
hideLoadingAnimation();
|
||||
}, 100);
|
||||
|
||||
// 初始化导航栏
|
||||
initNavigation();
|
||||
|
||||
// 初始化页面切换
|
||||
initPageTransitions();
|
||||
|
||||
// 初始化按钮交互
|
||||
initButtonInteractions();
|
||||
|
||||
// 初始化滚动效果
|
||||
initScrollEffects();
|
||||
|
||||
// 初始化图片懒加载
|
||||
initLazyLoading();
|
||||
|
||||
console.log('页面初始化完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化图片懒加载
|
||||
*/
|
||||
function initLazyLoading() {
|
||||
const lazyImages = document.querySelectorAll('img[data-src]');
|
||||
|
||||
if ('IntersectionObserver' in window) {
|
||||
const imageObserver = new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const img = entry.target;
|
||||
img.src = img.dataset.src;
|
||||
img.classList.remove('lazy-image');
|
||||
observer.unobserve(img);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
lazyImages.forEach(img => imageObserver.observe(img));
|
||||
} else {
|
||||
// 回退方案:直接加载所有图片
|
||||
lazyImages.forEach(img => {
|
||||
img.src = img.dataset.src;
|
||||
img.classList.remove('lazy-image');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能监控
|
||||
*/
|
||||
function initPerformanceMonitoring() {
|
||||
// 监控页面加载性能
|
||||
window.addEventListener('load', () => {
|
||||
if (window.performance) {
|
||||
const timing = performance.timing;
|
||||
const loadTime = timing.loadEventEnd - timing.navigationStart;
|
||||
console.log(`页面加载时间: ${loadTime}ms`);
|
||||
|
||||
if (loadTime > 3000) {
|
||||
console.warn('页面加载时间过长,建议优化');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 监控长任务
|
||||
if ('PerformanceObserver' in window) {
|
||||
const observer = new PerformanceObserver((list) => {
|
||||
for (const entry of list.getEntries()) {
|
||||
if (entry.duration > 50) {
|
||||
console.warn('检测到长任务:', entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe({ entryTypes: ['longtask'] });
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('DOMContentLoaded 事件触发');
|
||||
initPerformanceMonitoring();
|
||||
initPage();
|
||||
});
|
||||
|
||||
// 窗口加载完成事件作为最后保障
|
||||
window.addEventListener('load', () => {
|
||||
console.log('window.load 事件触发');
|
||||
// 确保加载动画被隐藏
|
||||
hideLoadingAnimation();
|
||||
});
|
||||
|
||||
// 立即隐藏加载动画(如果可能)
|
||||
if (document.readyState !== 'loading') {
|
||||
console.log('文档已加载完成,直接初始化');
|
||||
initPerformanceMonitoring();
|
||||
initPage();
|
||||
}
|
||||
|
||||
// 导出函数供其他模块使用
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = {
|
||||
initPage,
|
||||
navigateToPage,
|
||||
showMessage,
|
||||
debounce,
|
||||
throttle
|
||||
};
|
||||
}
|
||||
0
website/public/js/pages/architecture.js
Normal file
0
website/public/js/pages/architecture.js
Normal file
0
website/public/js/pages/features.js
Normal file
0
website/public/js/pages/features.js
Normal file
0
website/public/js/pages/home.js
Normal file
0
website/public/js/pages/home.js
Normal file
0
website/public/js/pages/plugins.js
Normal file
0
website/public/js/pages/plugins.js
Normal file
264
website/public/js/router.js
Normal file
264
website/public/js/router.js
Normal file
@@ -0,0 +1,264 @@
|
||||
// 前端路由系统
|
||||
|
||||
/**
|
||||
* 路由配置
|
||||
*/
|
||||
const routes = {
|
||||
'/': {
|
||||
name: 'home',
|
||||
title: 'FutureOSS - 插件化运行时框架',
|
||||
description: 'FutureOSS 是一个高度插件化的运行时框架,专为现代应用设计。'
|
||||
},
|
||||
'/features': {
|
||||
name: 'features',
|
||||
title: '核心特性 - FutureOSS',
|
||||
description: 'FutureOSS 的六大核心特性,专为现代开发需求设计。'
|
||||
},
|
||||
'/architecture': {
|
||||
name: 'architecture',
|
||||
title: '系统架构 - FutureOSS',
|
||||
description: '深入了解 FutureOSS 的微内核架构和插件化设计。'
|
||||
},
|
||||
'/plugins': {
|
||||
name: 'plugins',
|
||||
title: '插件生态 - FutureOSS',
|
||||
description: '探索 FutureOSS 丰富的插件生态系统。'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化路由
|
||||
*/
|
||||
function initRouter() {
|
||||
console.log('初始化前端路由...');
|
||||
|
||||
// 监听浏览器前进/后退
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
|
||||
// 拦截链接点击
|
||||
document.addEventListener('click', handleLinkClick);
|
||||
|
||||
// 初始路由处理
|
||||
handleRoute(window.location.pathname);
|
||||
|
||||
console.log('路由初始化完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理路由
|
||||
* @param {string} path - 路径
|
||||
*/
|
||||
function handleRoute(path) {
|
||||
const route = routes[path] || routes['/'];
|
||||
|
||||
// 更新页面状态
|
||||
updatePageState(route.name);
|
||||
|
||||
// 更新文档标题和描述
|
||||
document.title = route.title;
|
||||
updateMetaDescription(route.description);
|
||||
|
||||
// 触发路由变化事件
|
||||
window.dispatchEvent(new CustomEvent('routechange', {
|
||||
detail: { route: route.name, path: path }
|
||||
}));
|
||||
|
||||
console.log(`路由切换到: ${path} (${route.name})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新页面状态
|
||||
* @param {string} pageName - 页面名称
|
||||
*/
|
||||
function updatePageState(pageName) {
|
||||
// 更新body类名
|
||||
const bodyClass = document.body.className;
|
||||
const newClass = bodyClass.replace(/page-\w+/, `page-${pageName}`);
|
||||
document.body.className = newClass.includes('page-') ? newClass : `${newClass} page-${pageName}`;
|
||||
|
||||
// 更新导航栏激活状态
|
||||
updateNavActiveState(pageName);
|
||||
|
||||
// 滚动到顶部
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新导航栏激活状态
|
||||
* @param {string} pageName - 页面名称
|
||||
*/
|
||||
function updateNavActiveState(pageName) {
|
||||
const navLinks = document.querySelectorAll('.navbar-link');
|
||||
navLinks.forEach(link => {
|
||||
link.classList.remove('active');
|
||||
if (link.getAttribute('data-page') === pageName) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新meta描述
|
||||
* @param {string} description - 描述内容
|
||||
*/
|
||||
function updateMetaDescription(description) {
|
||||
let metaDescription = document.querySelector('meta[name="description"]');
|
||||
if (!metaDescription) {
|
||||
metaDescription = document.createElement('meta');
|
||||
metaDescription.name = 'description';
|
||||
document.head.appendChild(metaDescription);
|
||||
}
|
||||
metaDescription.content = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理链接点击
|
||||
* @param {Event} event - 点击事件
|
||||
*/
|
||||
function handleLinkClick(event) {
|
||||
const link = event.target.closest('a[data-page]');
|
||||
if (!link) return;
|
||||
|
||||
const href = link.getAttribute('href');
|
||||
const page = link.getAttribute('data-page');
|
||||
|
||||
// 如果是内部链接且不是当前页面
|
||||
if (href === '#' && page) {
|
||||
event.preventDefault();
|
||||
|
||||
const currentPage = document.body.className.match(/page-(\w+)/)?.[1];
|
||||
if (page === currentPage) return;
|
||||
|
||||
// 显示页面切换动画
|
||||
showPageTransition();
|
||||
|
||||
// 延迟导航
|
||||
setTimeout(() => {
|
||||
navigateTo(page);
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理浏览器前进/后退
|
||||
* @param {PopStateEvent} event - popstate事件
|
||||
*/
|
||||
function handlePopState(event) {
|
||||
if (event.state && event.state.page) {
|
||||
handleRoute(`/${event.state.page === 'home' ? '' : event.state.page}`);
|
||||
} else {
|
||||
handleRoute(window.location.pathname);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导航到指定页面
|
||||
* @param {string} pageName - 页面名称
|
||||
*/
|
||||
function navigateTo(pageName) {
|
||||
const path = pageName === 'home' ? '/' : `/${pageName}`;
|
||||
const route = routes[path];
|
||||
|
||||
if (!route) {
|
||||
console.error(`路由未找到: ${pageName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新浏览器历史
|
||||
window.history.pushState({ page: pageName }, route.title, path);
|
||||
|
||||
// 处理路由
|
||||
handleRoute(path);
|
||||
|
||||
// 隐藏页面切换动画
|
||||
hidePageTransition();
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示页面切换动画
|
||||
*/
|
||||
function showPageTransition() {
|
||||
const transition = document.getElementById('page-transition');
|
||||
if (transition) {
|
||||
transition.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏页面切换动画
|
||||
*/
|
||||
function hidePageTransition() {
|
||||
const transition = document.getElementById('page-transition');
|
||||
if (transition) {
|
||||
setTimeout(() => {
|
||||
transition.classList.remove('active');
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前路由信息
|
||||
* @returns {Object} 当前路由信息
|
||||
*/
|
||||
function getCurrentRoute() {
|
||||
const path = window.location.pathname;
|
||||
return routes[path] || routes['/'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路由是否存在
|
||||
* @param {string} path - 路径
|
||||
* @returns {boolean} 是否存在
|
||||
*/
|
||||
function routeExists(path) {
|
||||
return routes.hasOwnProperty(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加路由
|
||||
* @param {string} path - 路径
|
||||
* @param {Object} config - 路由配置
|
||||
*/
|
||||
function addRoute(path, config) {
|
||||
if (routes[path]) {
|
||||
console.warn(`路由已存在: ${path}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
routes[path] = {
|
||||
name: config.name || path.slice(1),
|
||||
title: config.title || 'FutureOSS',
|
||||
description: config.description || ''
|
||||
};
|
||||
|
||||
console.log(`路由添加成功: ${path}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除路由
|
||||
* @param {string} path - 路径
|
||||
*/
|
||||
function removeRoute(path) {
|
||||
if (!routes[path]) {
|
||||
console.warn(`路由不存在: ${path}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
delete routes[path];
|
||||
console.log(`路由移除成功: ${path}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 导出路由函数
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = {
|
||||
initRouter,
|
||||
navigateTo,
|
||||
getCurrentRoute,
|
||||
routeExists,
|
||||
addRoute,
|
||||
removeRoute,
|
||||
routes
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user