docs: 更新项目文档,完善需求和技术细节
This commit is contained in:
513
website/js/main.js
Normal file
513
website/js/main.js
Normal file
@@ -0,0 +1,513 @@
|
||||
// 主JavaScript文件 - 爱鉴花官方网站
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 初始化函数
|
||||
initWebsite();
|
||||
});
|
||||
|
||||
function initWebsite() {
|
||||
// 初始化导航栏滚动效果
|
||||
initNavbarScroll();
|
||||
|
||||
// 初始化滚动动画
|
||||
initScrollAnimation();
|
||||
|
||||
// 初始化表单验证
|
||||
initFormValidation();
|
||||
|
||||
// 初始化计数器动画
|
||||
initCounterAnimation();
|
||||
|
||||
// 初始化图片懒加载
|
||||
initLazyLoading();
|
||||
|
||||
// 初始化移动端菜单
|
||||
initMobileMenu();
|
||||
|
||||
// 初始化平滑滚动
|
||||
initSmoothScroll();
|
||||
|
||||
// 初始化悬停动画
|
||||
initHoverEffects();
|
||||
|
||||
// 初始化加载状态
|
||||
initLoadingStates();
|
||||
|
||||
// 初始化页面过渡效果
|
||||
initPageTransitions();
|
||||
}
|
||||
|
||||
// 导航栏滚动效果
|
||||
function initNavbarScroll() {
|
||||
const navbar = document.querySelector('.navbar');
|
||||
|
||||
window.addEventListener('scroll', function() {
|
||||
if (window.scrollY > 100) {
|
||||
navbar.classList.add('navbar-scrolled');
|
||||
} else {
|
||||
navbar.classList.remove('navbar-scrolled');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 滚动动画效果
|
||||
function initScrollAnimation() {
|
||||
const animatedElements = document.querySelectorAll('.card, .feature-icon');
|
||||
|
||||
const observerOptions = {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -50px 0px'
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(function(entries) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('fade-in-up');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
animatedElements.forEach(function(element) {
|
||||
observer.observe(element);
|
||||
});
|
||||
}
|
||||
|
||||
// 表单验证
|
||||
function initFormValidation() {
|
||||
const forms = document.querySelectorAll('form');
|
||||
|
||||
forms.forEach(function(form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
if (!validateForm(form)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function validateForm(form) {
|
||||
let isValid = true;
|
||||
const inputs = form.querySelectorAll('input[required], textarea[required]');
|
||||
|
||||
inputs.forEach(function(input) {
|
||||
if (!input.value.trim()) {
|
||||
showError(input, '此字段为必填项');
|
||||
isValid = false;
|
||||
} else if (input.type === 'email' && !isValidEmail(input.value)) {
|
||||
showError(input, '请输入有效的邮箱地址');
|
||||
isValid = false;
|
||||
} else {
|
||||
clearError(input);
|
||||
}
|
||||
});
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
function isValidEmail(email) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
function showError(input, message) {
|
||||
clearError(input);
|
||||
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'invalid-feedback';
|
||||
errorDiv.textContent = message;
|
||||
|
||||
input.classList.add('is-invalid');
|
||||
input.parentNode.appendChild(errorDiv);
|
||||
}
|
||||
|
||||
function clearError(input) {
|
||||
input.classList.remove('is-invalid');
|
||||
const errorDiv = input.parentNode.querySelector('.invalid-feedback');
|
||||
if (errorDiv) {
|
||||
errorDiv.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 计数器动画
|
||||
function initCounterAnimation() {
|
||||
const counters = document.querySelectorAll('.counter');
|
||||
|
||||
if (counters.length > 0) {
|
||||
const observer = new IntersectionObserver(function(entries) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.isIntersecting) {
|
||||
animateCounters();
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(counters[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function animateCounters() {
|
||||
const counters = document.querySelectorAll('.counter');
|
||||
|
||||
counters.forEach(function(counter) {
|
||||
const target = parseInt(counter.getAttribute('data-target'));
|
||||
const duration = 2000; // 2 seconds
|
||||
const frameDuration = 1000 / 60; // 60fps
|
||||
const totalFrames = Math.round(duration / frameDuration);
|
||||
let currentFrame = 0;
|
||||
|
||||
const updateCounter = function() {
|
||||
currentFrame++;
|
||||
const progress = currentFrame / totalFrames;
|
||||
const currentValue = Math.round(target * progress);
|
||||
|
||||
counter.textContent = currentValue.toLocaleString();
|
||||
|
||||
if (currentFrame < totalFrames) {
|
||||
requestAnimationFrame(updateCounter);
|
||||
} else {
|
||||
counter.textContent = target.toLocaleString();
|
||||
}
|
||||
};
|
||||
|
||||
updateCounter();
|
||||
});
|
||||
}
|
||||
|
||||
// 图片懒加载
|
||||
function initLazyLoading() {
|
||||
if ('IntersectionObserver' in window) {
|
||||
const lazyImages = document.querySelectorAll('img.lazy-load');
|
||||
|
||||
const imageObserver = new IntersectionObserver(function(entries) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.isIntersecting) {
|
||||
const img = entry.target;
|
||||
// 确保图片完全加载后显示
|
||||
img.onload = function() {
|
||||
img.classList.add('loaded');
|
||||
};
|
||||
// 处理data-src属性或直接使用src
|
||||
if (img.hasAttribute('data-src')) {
|
||||
img.src = img.getAttribute('data-src');
|
||||
img.removeAttribute('data-src');
|
||||
}
|
||||
imageObserver.unobserve(img);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.1,
|
||||
rootMargin: '50px 0px'
|
||||
});
|
||||
|
||||
lazyImages.forEach(function(img) {
|
||||
imageObserver.observe(img);
|
||||
});
|
||||
} else {
|
||||
// 浏览器不支持IntersectionObserver时的降级方案
|
||||
const lazyImages = document.querySelectorAll('img.lazy-load');
|
||||
lazyImages.forEach(function(img) {
|
||||
img.classList.add('loaded');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 移动端菜单处理
|
||||
function initMobileMenu() {
|
||||
const navbarToggler = document.querySelector('.navbar-toggler');
|
||||
const navbarCollapse = document.querySelector('.navbar-collapse');
|
||||
|
||||
if (navbarToggler && navbarCollapse) {
|
||||
navbarToggler.addEventListener('click', function() {
|
||||
navbarCollapse.classList.toggle('show');
|
||||
// 添加菜单动画效果
|
||||
navbarToggler.classList.toggle('active');
|
||||
});
|
||||
|
||||
// 点击菜单项后自动关闭菜单(移动端)
|
||||
const navLinks = document.querySelectorAll('.navbar-nav .nav-link');
|
||||
navLinks.forEach(function(link) {
|
||||
link.addEventListener('click', function() {
|
||||
if (window.innerWidth < 992) {
|
||||
navbarCollapse.classList.remove('show');
|
||||
navbarToggler.classList.remove('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 平滑滚动效果
|
||||
function initSmoothScroll() {
|
||||
// 内部链接平滑滚动
|
||||
const internalLinks = document.querySelectorAll('a[href^="#"]');
|
||||
|
||||
internalLinks.forEach(function(link) {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const targetId = this.getAttribute('href').substring(1);
|
||||
const targetElement = document.getElementById(targetId);
|
||||
|
||||
if (targetElement) {
|
||||
const offsetTop = targetElement.offsetTop - 80; // 考虑导航栏高度
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetTop,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 返回顶部按钮
|
||||
const backToTopBtn = document.createElement('button');
|
||||
backToTopBtn.className = 'back-to-top';
|
||||
backToTopBtn.innerHTML = '↑';
|
||||
backToTopBtn.setAttribute('aria-label', '返回顶部');
|
||||
|
||||
backToTopBtn.addEventListener('click', function() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
});
|
||||
|
||||
// 滚动时显示/隐藏返回顶部按钮
|
||||
window.addEventListener('scroll', debounce(function() {
|
||||
if (window.scrollY > 500) {
|
||||
backToTopBtn.classList.add('show');
|
||||
} else {
|
||||
backToTopBtn.classList.remove('show');
|
||||
}
|
||||
}, 100));
|
||||
|
||||
document.body.appendChild(backToTopBtn);
|
||||
}
|
||||
|
||||
// 悬停动画效果
|
||||
function initHoverEffects() {
|
||||
// 卡片悬停效果
|
||||
const cards = document.querySelectorAll('.card, .product-card, .news-card');
|
||||
|
||||
cards.forEach(function(card) {
|
||||
card.addEventListener('mouseenter', function() {
|
||||
this.style.transform = 'translateY(-5px) scale(1.02)';
|
||||
this.style.boxShadow = '0 10px 25px rgba(0, 0, 0, 0.15)';
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', function() {
|
||||
this.style.transform = 'translateY(0) scale(1)';
|
||||
this.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
|
||||
});
|
||||
});
|
||||
|
||||
// 按钮悬停效果
|
||||
const buttons = document.querySelectorAll('.btn, .nav-link');
|
||||
|
||||
buttons.forEach(function(button) {
|
||||
button.addEventListener('mouseenter', function() {
|
||||
this.style.transform = 'translateY(-2px)';
|
||||
});
|
||||
|
||||
button.addEventListener('mouseleave', function() {
|
||||
this.style.transform = 'translateY(0)';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 加载状态指示器
|
||||
function initLoadingStates() {
|
||||
const forms = document.querySelectorAll('form');
|
||||
|
||||
forms.forEach(function(form) {
|
||||
form.addEventListener('submit', function() {
|
||||
const submitBtn = this.querySelector('button[type="submit"]');
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 处理中...';
|
||||
|
||||
// 3秒后恢复按钮状态(防止无限加载)
|
||||
setTimeout(function() {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = submitBtn.getAttribute('data-original-text') || '提交';
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 保存按钮原始文本
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const submitButtons = document.querySelectorAll('button[type="submit"]');
|
||||
submitButtons.forEach(function(btn) {
|
||||
btn.setAttribute('data-original-text', btn.textContent);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 页面过渡效果
|
||||
function initPageTransitions() {
|
||||
// 链接点击时的过渡效果
|
||||
const links = document.querySelectorAll('a:not([href^="#"]):not([href^="javascript"])');
|
||||
|
||||
links.forEach(function(link) {
|
||||
link.addEventListener('click', function(e) {
|
||||
if (this.href && !this.target && !this.hasAttribute('download')) {
|
||||
e.preventDefault();
|
||||
|
||||
// 添加页面离开动画
|
||||
document.body.classList.add('page-leaving');
|
||||
|
||||
setTimeout(function() {
|
||||
window.location.href = link.href;
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 页面进入动画
|
||||
document.body.classList.add('page-entering');
|
||||
|
||||
setTimeout(function() {
|
||||
document.body.classList.remove('page-entering');
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// 工具函数:防抖
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function() {
|
||||
const context = this;
|
||||
const args = arguments;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(function() {
|
||||
func.apply(context, args);
|
||||
}, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// 工具函数:节流
|
||||
function throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function() {
|
||||
const args = arguments;
|
||||
const context = this;
|
||||
if (!inThrottle) {
|
||||
func.apply(context, args);
|
||||
inThrottle = true;
|
||||
setTimeout(function() {
|
||||
inThrottle = false;
|
||||
}, limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 页面性能监控
|
||||
function monitorPerformance() {
|
||||
// 页面加载时间
|
||||
window.addEventListener('load', function() {
|
||||
const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart;
|
||||
console.log('页面加载时间:', loadTime + 'ms');
|
||||
});
|
||||
|
||||
// 最大内容绘制(LCP)
|
||||
new PerformanceObserver(function(entryList) {
|
||||
const entries = entryList.getEntries();
|
||||
const lastEntry = entries[entries.length - 1];
|
||||
console.log('LCP:', lastEntry.startTime);
|
||||
}).observe({type: 'largest-contentful-paint', buffered: true});
|
||||
}
|
||||
|
||||
// 错误监控
|
||||
function monitorErrors() {
|
||||
window.addEventListener('error', function(e) {
|
||||
console.error('JavaScript错误:', e.error);
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', function(e) {
|
||||
console.error('未处理的Promise拒绝:', e.reason);
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化性能和错误监控
|
||||
monitorPerformance();
|
||||
monitorErrors();
|
||||
|
||||
// 通用JavaScript功能
|
||||
|
||||
// 导航栏滚动效果
|
||||
window.addEventListener('scroll', function() {
|
||||
const navbar = document.querySelector('.navbar');
|
||||
if (window.scrollY > 50) {
|
||||
navbar.classList.add('scrolled');
|
||||
} else {
|
||||
navbar.classList.remove('scrolled');
|
||||
}
|
||||
});
|
||||
|
||||
// 懒加载图片
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const lazyImages = [].slice.call(document.querySelectorAll('img.lazy-load'));
|
||||
|
||||
if ('IntersectionObserver' in window) {
|
||||
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.isIntersecting) {
|
||||
let lazyImage = entry.target;
|
||||
lazyImage.src = lazyImage.dataset.src || lazyImage.src;
|
||||
lazyImage.classList.remove('lazy-load');
|
||||
lazyImageObserver.unobserve(lazyImage);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
lazyImages.forEach(function(lazyImage) {
|
||||
lazyImageObserver.observe(lazyImage);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 平滑滚动
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
window.scrollTo({
|
||||
top: target.offsetTop - 76,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 表单验证增强
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
// 获取所有表单并阻止默认提交行为
|
||||
var forms = document.getElementsByClassName('needs-validation');
|
||||
var validation = Array.prototype.filter.call(forms, function(form) {
|
||||
form.addEventListener('submit', function(event) {
|
||||
if (form.checkValidity() === false) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
form.classList.add('was-validated');
|
||||
}, false);
|
||||
});
|
||||
}, false);
|
||||
})();
|
||||
|
||||
// 动态年份更新
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const yearElements = document.querySelectorAll('.current-year');
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
yearElements.forEach(element => {
|
||||
element.textContent = currentYear;
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user