399 lines
12 KiB
JavaScript
399 lines
12 KiB
JavaScript
// 个人作品展示网站 - 主JavaScript文件
|
||
// 文件位置: portfolio/js/main.js
|
||
// 创建时间: 2026-03-31
|
||
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// 初始化所有功能
|
||
initNavigation();
|
||
initSmoothScroll();
|
||
initContactForm();
|
||
initBackToTop();
|
||
initSkillAnimations();
|
||
initProjectCards();
|
||
|
||
console.log('个人作品展示网站已加载完成!');
|
||
});
|
||
|
||
// 导航功能
|
||
function initNavigation() {
|
||
const menuToggle = document.querySelector('.menu-toggle');
|
||
const navLinks = document.querySelector('.nav-links');
|
||
const navItems = document.querySelectorAll('.nav-link');
|
||
|
||
// 菜单切换
|
||
if (menuToggle) {
|
||
menuToggle.addEventListener('click', function() {
|
||
navLinks.classList.toggle('active');
|
||
menuToggle.classList.toggle('active');
|
||
});
|
||
}
|
||
|
||
// 导航项点击
|
||
navItems.forEach(item => {
|
||
item.addEventListener('click', function(e) {
|
||
// 关闭移动端菜单
|
||
if (window.innerWidth <= 1023) {
|
||
navLinks.classList.remove('active');
|
||
menuToggle.classList.remove('active');
|
||
}
|
||
|
||
// 更新活动状态
|
||
navItems.forEach(nav => nav.classList.remove('active'));
|
||
this.classList.add('active');
|
||
});
|
||
});
|
||
|
||
// 滚动时更新活动导航项
|
||
window.addEventListener('scroll', updateActiveNav);
|
||
}
|
||
|
||
// 平滑滚动
|
||
function initSmoothScroll() {
|
||
const links = document.querySelectorAll('a[href^="#"]');
|
||
|
||
links.forEach(link => {
|
||
link.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
|
||
const targetId = this.getAttribute('href');
|
||
if (targetId === '#') return;
|
||
|
||
const targetElement = document.querySelector(targetId);
|
||
if (targetElement) {
|
||
const headerHeight = document.querySelector('.navbar').offsetHeight;
|
||
const targetPosition = targetElement.offsetTop - headerHeight;
|
||
|
||
window.scrollTo({
|
||
top: targetPosition,
|
||
behavior: 'smooth'
|
||
});
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 联系表单处理
|
||
function initContactForm() {
|
||
const contactForm = document.getElementById('contactForm');
|
||
|
||
if (contactForm) {
|
||
contactForm.addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
|
||
// 获取表单数据
|
||
const formData = new FormData(this);
|
||
const data = Object.fromEntries(formData);
|
||
|
||
// 简单验证
|
||
if (!validateForm(data)) {
|
||
return;
|
||
}
|
||
|
||
// 显示加载状态
|
||
const submitBtn = this.querySelector('button[type="submit"]');
|
||
const originalText = submitBtn.textContent;
|
||
submitBtn.textContent = '发送中...';
|
||
submitBtn.disabled = true;
|
||
|
||
// 模拟API调用
|
||
setTimeout(() => {
|
||
// 在实际应用中,这里会发送到服务器
|
||
console.log('表单数据:', data);
|
||
|
||
// 显示成功消息
|
||
showFormMessage('消息发送成功!我会尽快回复您。', 'success');
|
||
|
||
// 重置表单
|
||
contactForm.reset();
|
||
|
||
// 恢复按钮状态
|
||
submitBtn.textContent = originalText;
|
||
submitBtn.disabled = false;
|
||
}, 1500);
|
||
});
|
||
}
|
||
|
||
// 表单验证
|
||
function validateForm(data) {
|
||
const errors = [];
|
||
|
||
if (!data.name || data.name.trim().length < 2) {
|
||
errors.push('请输入有效的姓名(至少2个字符)');
|
||
}
|
||
|
||
if (!data.email || !isValidEmail(data.email)) {
|
||
errors.push('请输入有效的邮箱地址');
|
||
}
|
||
|
||
if (!data.subject || data.subject.trim().length < 3) {
|
||
errors.push('请输入主题(至少3个字符)');
|
||
}
|
||
|
||
if (!data.message || data.message.trim().length < 10) {
|
||
errors.push('请输入消息内容(至少10个字符)');
|
||
}
|
||
|
||
if (errors.length > 0) {
|
||
showFormMessage(errors.join('<br>'), 'error');
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
function isValidEmail(email) {
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
return emailRegex.test(email);
|
||
}
|
||
|
||
function showFormMessage(message, type) {
|
||
// 移除旧的消息
|
||
const oldMessage = document.querySelector('.form-message');
|
||
if (oldMessage) {
|
||
oldMessage.remove();
|
||
}
|
||
|
||
// 创建新消息
|
||
const messageDiv = document.createElement('div');
|
||
messageDiv.className = `form-message ${type}`;
|
||
messageDiv.innerHTML = message;
|
||
|
||
// 添加到表单前
|
||
const form = document.getElementById('contactForm');
|
||
form.parentNode.insertBefore(messageDiv, form);
|
||
|
||
// 5秒后自动移除
|
||
setTimeout(() => {
|
||
messageDiv.remove();
|
||
}, 5000);
|
||
}
|
||
}
|
||
|
||
// 返回顶部按钮
|
||
function initBackToTop() {
|
||
const backToTopBtn = document.getElementById('backToTop');
|
||
|
||
if (backToTopBtn) {
|
||
// 显示/隐藏按钮
|
||
window.addEventListener('scroll', function() {
|
||
if (window.pageYOffset > 300) {
|
||
backToTopBtn.style.display = 'block';
|
||
setTimeout(() => {
|
||
backToTopBtn.style.opacity = '1';
|
||
}, 10);
|
||
} else {
|
||
backToTopBtn.style.opacity = '0';
|
||
setTimeout(() => {
|
||
backToTopBtn.style.display = 'none';
|
||
}, 300);
|
||
}
|
||
});
|
||
|
||
// 点击返回顶部
|
||
backToTopBtn.addEventListener('click', function() {
|
||
window.scrollTo({
|
||
top: 0,
|
||
behavior: 'smooth'
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
// 技能条动画
|
||
function initSkillAnimations() {
|
||
const skillBars = document.querySelectorAll('.skill-level');
|
||
|
||
// 创建Intersection Observer来检测技能部分是否在视口中
|
||
const observer = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
// 当技能部分进入视口时,触发动画
|
||
skillBars.forEach(bar => {
|
||
const width = bar.style.width;
|
||
bar.style.width = '0';
|
||
|
||
setTimeout(() => {
|
||
bar.style.width = width;
|
||
}, 100);
|
||
});
|
||
|
||
// 停止观察,避免重复触发
|
||
observer.unobserve(entry.target);
|
||
}
|
||
});
|
||
}, {
|
||
threshold: 0.5 // 当50%的元素可见时触发
|
||
});
|
||
|
||
// 观察技能部分
|
||
const skillsSection = document.getElementById('skills');
|
||
if (skillsSection) {
|
||
observer.observe(skillsSection);
|
||
}
|
||
}
|
||
|
||
// 项目卡片交互
|
||
function initProjectCards() {
|
||
const projectCards = document.querySelectorAll('.project-card');
|
||
|
||
projectCards.forEach(card => {
|
||
// 点击卡片查看详情
|
||
card.addEventListener('click', function(e) {
|
||
// 防止点击链接时触发卡片点击
|
||
if (e.target.tagName === 'A') return;
|
||
|
||
// 在实际应用中,这里会打开项目详情模态框
|
||
console.log('查看项目详情:', this.querySelector('h3').textContent);
|
||
});
|
||
|
||
// 键盘导航支持
|
||
card.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Enter' || e.key === ' ') {
|
||
e.preventDefault();
|
||
console.log('查看项目详情:', this.querySelector('h3').textContent);
|
||
}
|
||
});
|
||
|
||
// 设置卡片可聚焦
|
||
card.setAttribute('tabindex', '0');
|
||
});
|
||
}
|
||
|
||
// 更新活动导航项
|
||
function updateActiveNav() {
|
||
const sections = document.querySelectorAll('section[id]');
|
||
const navLinks = document.querySelectorAll('.nav-link');
|
||
const scrollPosition = window.pageYOffset + 100;
|
||
|
||
sections.forEach(section => {
|
||
const sectionTop = section.offsetTop;
|
||
const sectionHeight = section.offsetHeight;
|
||
const sectionId = section.getAttribute('id');
|
||
|
||
if (scrollPosition >= sectionTop && scrollPosition < sectionTop + sectionHeight) {
|
||
navLinks.forEach(link => {
|
||
link.classList.remove('active');
|
||
if (link.getAttribute('href') === `#${sectionId}`) {
|
||
link.classList.add('active');
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
// 性能优化:延迟加载图片
|
||
function initLazyLoading() {
|
||
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.add('loaded');
|
||
observer.unobserve(img);
|
||
}
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('img[data-src]').forEach(img => {
|
||
imageObserver.observe(img);
|
||
});
|
||
}
|
||
}
|
||
|
||
// 添加CSS样式到页面
|
||
function addDynamicStyles() {
|
||
const styles = `
|
||
.form-message {
|
||
padding: 12px 16px;
|
||
margin-bottom: 20px;
|
||
border-radius: 8px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.form-message.success {
|
||
background-color: rgba(76, 175, 80, 0.1);
|
||
color: #4caf50;
|
||
border: 1px solid #4caf50;
|
||
}
|
||
|
||
.form-message.error {
|
||
background-color: rgba(244, 67, 54, 0.1);
|
||
color: #f44336;
|
||
border: 1px solid #f44336;
|
||
}
|
||
|
||
.back-to-top {
|
||
display: none;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
background-color: var(--color-highlight);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
font-size: 1.2rem;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.back-to-top:hover {
|
||
background-color: #ff2e4f;
|
||
}
|
||
|
||
.project-card {
|
||
cursor: pointer;
|
||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||
}
|
||
|
||
.project-card:focus {
|
||
outline: 2px solid var(--color-highlight);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
img[data-src] {
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
img.loaded {
|
||
opacity: 1;
|
||
}
|
||
`;
|
||
|
||
const styleSheet = document.createElement('style');
|
||
styleSheet.textContent = styles;
|
||
document.head.appendChild(styleSheet);
|
||
}
|
||
|
||
// 初始化动态样式
|
||
addDynamicStyles();
|
||
|
||
// 性能监控
|
||
if (typeof PerformanceObserver !== 'undefined') {
|
||
const observer = new PerformanceObserver((list) => {
|
||
for (const entry of list.getEntries()) {
|
||
console.log(`${entry.name}: ${entry.duration}ms`);
|
||
}
|
||
});
|
||
|
||
observer.observe({ entryTypes: ['measure'] });
|
||
}
|
||
|
||
// 错误处理
|
||
window.addEventListener('error', function(e) {
|
||
console.error('JavaScript错误:', e.message, 'at', e.filename, ':', e.lineno);
|
||
|
||
// 在实际应用中,这里可以发送错误到监控服务
|
||
// sendErrorToMonitoring(e);
|
||
});
|
||
|
||
// PWA支持(基础)
|
||
if ('serviceWorker' in navigator && window.location.protocol === 'https:') {
|
||
window.addEventListener('load', function() {
|
||
navigator.serviceWorker.register('/sw.js').then(function(registration) {
|
||
console.log('ServiceWorker 注册成功:', registration.scope);
|
||
}).catch(function(error) {
|
||
console.log('ServiceWorker 注册失败:', error);
|
||
});
|
||
});
|
||
} |