Files
2026-04-02 00:59:42 +08:00

396 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 主JavaScript文件 - 个人作品展示网站
// DOM加载完成后执行
document.addEventListener('DOMContentLoaded', function() {
console.log('个人作品展示网站已加载');
// 初始化所有模块
initNavigation();
initSmoothScroll();
initScrollEffects();
initContactForm();
initBackToTop();
initSkillAnimations();
// 性能监控
monitorPerformance();
});
// 导航功能
function initNavigation() {
const navbar = document.querySelector('.navbar');
const navbarToggle = document.querySelector('.navbar__toggle');
const navbarMenu = document.querySelector('.navbar__menu');
const navbarLinks = document.querySelectorAll('.navbar__link');
if (!navbarToggle || !navbarMenu) return;
// 切换移动端菜单
navbarToggle.addEventListener('click', function() {
const isExpanded = this.getAttribute('aria-expanded') === 'true';
this.setAttribute('aria-expanded', !isExpanded);
navbarMenu.classList.toggle('active');
// 更新汉堡菜单图标
const icons = this.querySelectorAll('.navbar__toggle-icon');
if (!isExpanded) {
icons[0].style.transform = 'rotate(45deg) translate(5px, 5px)';
icons[1].style.opacity = '0';
icons[2].style.transform = 'rotate(-45deg) translate(7px, -6px)';
} else {
icons[0].style.transform = 'none';
icons[1].style.opacity = '1';
icons[2].style.transform = 'none';
}
});
// 点击导航链接时关闭移动端菜单
navbarLinks.forEach(link => {
link.addEventListener('click', function() {
if (window.innerWidth <= 1023) {
navbarMenu.classList.remove('active');
navbarToggle.setAttribute('aria-expanded', 'false');
const icons = navbarToggle.querySelectorAll('.navbar__toggle-icon');
icons[0].style.transform = 'none';
icons[1].style.opacity = '1';
icons[2].style.transform = 'none';
}
// 更新活动链接
navbarLinks.forEach(l => l.classList.remove('active'));
this.classList.add('active');
});
});
// 滚动时更新导航栏样式和活动链接
window.addEventListener('scroll', function() {
// 导航栏滚动效果
if (window.scrollY > 50) {
navbar.classList.add('scrolled');
} else {
navbar.classList.remove('scrolled');
}
// 更新活动导航链接
updateActiveNavLink();
});
// 点击外部区域关闭菜单
document.addEventListener('click', function(event) {
if (!navbar.contains(event.target) && navbarMenu.classList.contains('active')) {
navbarMenu.classList.remove('active');
navbarToggle.setAttribute('aria-expanded', 'false');
const icons = navbarToggle.querySelectorAll('.navbar__toggle-icon');
icons[0].style.transform = 'none';
icons[1].style.opacity = '1';
icons[2].style.transform = 'none';
}
});
}
// 平滑滚动
function initSmoothScroll() {
// 使用CSS的scroll-behavior: smooth,这里作为后备方案
if (!('scrollBehavior' in document.documentElement.style)) {
const smoothScrollLinks = document.querySelectorAll('a[href^="#"]');
smoothScrollLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href');
if (targetId === '#') return;
const targetElement = document.querySelector(targetId);
if (!targetElement) return;
const navbarHeight = document.querySelector('.navbar').offsetHeight;
const targetPosition = targetElement.offsetTop - navbarHeight;
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
});
});
});
}
}
// 滚动效果
function initScrollEffects() {
const observerOptions = {
root: null,
rootMargin: '0px',
threshold: 0.1
};
const observer = new IntersectionObserver(function(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in');
}
});
}, observerOptions);
// 观察需要动画的元素
const animatedElements = document.querySelectorAll('.skill__progress, .project__card, .timeline__item');
animatedElements.forEach(element => {
observer.observe(element);
});
}
// 联系表单处理
function initContactForm() {
const contactForm = document.querySelector('.contact__form');
if (!contactForm) return;
const form = contactForm.querySelector('form');
if (!form) return;
const submitButton = form.querySelector('.form__button');
const messageDiv = form.querySelector('.form__message');
form.addEventListener('submit', async function(e) {
e.preventDefault();
// 禁用提交按钮
submitButton.disabled = true;
submitButton.innerHTML = '<span class="loading"></span> 发送中...';
// 收集表单数据
const formData = new FormData(form);
const data = Object.fromEntries(formData);
try {
// 这里应该是实际的API调用
// 暂时使用模拟延迟
await new Promise(resolve => setTimeout(resolve, 1500));
// 显示成功消息
showFormMessage('消息发送成功!我会尽快回复您。', 'success');
form.reset();
// 记录到控制台(实际项目中应该发送到服务器)
console.log('联系表单提交:', data);
} catch (error) {
console.error('表单提交错误:', error);
showFormMessage('发送失败,请稍后重试。', 'error');
} finally {
// 恢复提交按钮
submitButton.disabled = false;
submitButton.textContent = '发送消息';
}
});
// 显示表单消息
function showFormMessage(message, type) {
if (!messageDiv) return;
messageDiv.textContent = message;
messageDiv.className = `form__message form__message--${type}`;
messageDiv.style.display = 'block';
// 3秒后隐藏消息
setTimeout(() => {
messageDiv.style.display = 'none';
}, 3000);
}
// 表单验证
const inputs = form.querySelectorAll('.form__input, .form__textarea');
inputs.forEach(input => {
input.addEventListener('blur', function() {
validateField(this);
});
input.addEventListener('input', function() {
clearFieldError(this);
});
});
function validateField(field) {
const value = field.value.trim();
let isValid = true;
let errorMessage = '';
if (field.hasAttribute('required') && !value) {
isValid = false;
errorMessage = '此字段为必填项';
} else if (field.type === 'email' && value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
isValid = false;
errorMessage = '请输入有效的邮箱地址';
}
}
if (!isValid) {
showFieldError(field, errorMessage);
} else {
clearFieldError(field);
}
return isValid;
}
function showFieldError(field, message) {
clearFieldError(field);
const errorDiv = document.createElement('div');
errorDiv.className = 'form__error';
errorDiv.textContent = message;
errorDiv.style.color = '#e74c3c';
errorDiv.style.fontSize = '0.875rem';
errorDiv.style.marginTop = '0.25rem';
field.parentNode.appendChild(errorDiv);
field.style.borderColor = '#e74c3c';
}
function clearFieldError(field) {
const errorDiv = field.parentNode.querySelector('.form__error');
if (errorDiv) {
errorDiv.remove();
}
field.style.borderColor = '';
}
}
// 返回顶部按钮
function initBackToTop() {
const backToTopButton = document.querySelector('.footer__back-to-top');
if (!backToTopButton) return;
window.addEventListener('scroll', function() {
if (window.scrollY > 500) {
backToTopButton.classList.add('visible');
} else {
backToTopButton.classList.remove('visible');
}
});
backToTopButton.addEventListener('click', function(e) {
e.preventDefault();
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
}
// 技能进度条动画
function initSkillAnimations() {
const skillProgressBars = document.querySelectorAll('.skill__progress');
const observer = new IntersectionObserver(function(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const progressBar = entry.target;
const width = progressBar.style.width;
// 重置宽度为0,然后动画到目标宽度
progressBar.style.width = '0%';
setTimeout(() => {
progressBar.style.width = width;
}, 100);
// 停止观察
observer.unobserve(progressBar);
}
});
}, {
threshold: 0.5
});
skillProgressBars.forEach(bar => {
observer.observe(bar);
});
}
// 更新活动导航链接
function updateActiveNavLink() {
const sections = document.querySelectorAll('section[id]');
const navLinks = document.querySelectorAll('.navbar__link');
let currentSection = '';
sections.forEach(section => {
const sectionTop = section.offsetTop;
const sectionHeight = section.clientHeight;
const navbarHeight = document.querySelector('.navbar').offsetHeight;
if (window.scrollY >= sectionTop - navbarHeight - 100) {
currentSection = section.getAttribute('id');
}
});
navLinks.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href') === `#${currentSection}`) {
link.classList.add('active');
}
});
}
// 性能监控
function monitorPerformance() {
// 记录页面加载时间
window.addEventListener('load', function() {
const timing = performance.timing;
const loadTime = timing.loadEventEnd - timing.navigationStart;
console.log(`页面加载时间: ${loadTime}ms`);
// 如果加载时间过长,记录警告
if (loadTime > 3000) {
console.warn('页面加载时间较长,建议优化');
}
});
// 监控资源加载
const resources = performance.getEntriesByType('resource');
resources.forEach(resource => {
if (resource.duration > 1000) {
console.warn(`资源加载较慢: ${resource.name} (${resource.duration}ms)`);
}
});
}
// 工具函数
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// 导出函数供其他模块使用
window.PortfolioApp = {
initNavigation,
initSmoothScroll,
initContactForm,
updateActiveNavLink,
debounce,
throttle
};