Files
jiebanke/website/js/enhanced-interactions.js

590 lines
18 KiB
JavaScript

/**
* 增强交互体验管理器
* 统一管理整个网站的交互优化
*/
class EnhancedInteractionManager {
constructor() {
this.init();
}
init() {
this.setupGlobalInteractions();
this.setupLoadingStates();
this.setupFormEnhancements();
this.setupScrollEffects();
this.setupTooltips();
this.setupNotifications();
this.setupKeyboardNavigation();
this.setupMobileOptimizations();
}
// 全局交互设置
setupGlobalInteractions() {
// 平滑滚动
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// 返回顶部按钮
this.createBackToTopButton();
// 页面加载进度条
this.createLoadingBar();
// 全局错误处理
this.setupGlobalErrorHandling();
}
// 创建返回顶部按钮
createBackToTopButton() {
const backToTop = document.createElement('button');
backToTop.innerHTML = '<i class="fa fa-chevron-up"></i>';
backToTop.className = 'btn-back-to-top';
backToTop.setAttribute('aria-label', '返回顶部');
const style = document.createElement('style');
style.textContent = `
.btn-back-to-top {
position: fixed;
bottom: 30px;
right: 30px;
width: 50px;
height: 50px;
background: #007bff;
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0,123,255,0.3);
}
.btn-back-to-top:hover {
background: #0056b3;
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0,123,255,0.4);
}
.btn-back-to-top.show {
opacity: 1;
visibility: visible;
}
@media (max-width: 768px) {
.btn-back-to-top {
bottom: 20px;
right: 20px;
width: 45px;
height: 45px;
}
}
`;
document.head.appendChild(style);
document.body.appendChild(backToTop);
// 滚动显示/隐藏
window.addEventListener('scroll', () => {
if (window.pageYOffset > 300) {
backToTop.classList.add('show');
} else {
backToTop.classList.remove('show');
}
});
// 点击返回顶部
backToTop.addEventListener('click', () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
}
// 创建加载进度条
createLoadingBar() {
const loadingBar = document.createElement('div');
loadingBar.className = 'loading-bar';
const style = document.createElement('style');
style.textContent = `
.loading-bar {
position: fixed;
top: 0;
left: 0;
width: 0%;
height: 3px;
background: linear-gradient(90deg, #007bff, #28a745);
z-index: 9999;
transition: width 0.3s ease;
}
`;
document.head.appendChild(style);
document.body.appendChild(loadingBar);
// 页面加载进度
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 30;
if (progress > 90) progress = 90;
loadingBar.style.width = progress + '%';
}, 200);
window.addEventListener('load', () => {
clearInterval(interval);
loadingBar.style.width = '100%';
setTimeout(() => {
loadingBar.style.opacity = '0';
setTimeout(() => loadingBar.remove(), 300);
}, 200);
});
}
// 设置加载状态
setupLoadingStates() {
// 为所有按钮添加加载状态
document.addEventListener('click', (e) => {
if (e.target.matches('button[type="submit"], .btn-primary, .btn-flower, .btn-animal')) {
this.showButtonLoading(e.target);
}
});
}
// 显示按钮加载状态
showButtonLoading(button) {
const originalText = button.innerHTML;
const originalDisabled = button.disabled;
button.disabled = true;
button.innerHTML = '<i class="fa fa-spinner fa-spin me-2"></i>处理中...';
// 模拟加载时间
setTimeout(() => {
button.disabled = originalDisabled;
button.innerHTML = originalText;
}, 2000);
}
// 表单增强
setupFormEnhancements() {
// 实时验证
document.querySelectorAll('input, textarea, select').forEach(field => {
field.addEventListener('blur', () => this.validateField(field));
field.addEventListener('input', () => this.clearFieldError(field));
});
// 表单提交增强
document.querySelectorAll('form').forEach(form => {
form.addEventListener('submit', (e) => {
if (!this.validateForm(form)) {
e.preventDefault();
}
});
});
}
// 验证单个字段
validateField(field) {
const value = field.value.trim();
let isValid = true;
let message = '';
// 必填验证
if (field.hasAttribute('required') && !value) {
isValid = false;
message = '此字段为必填项';
}
// 邮箱验证
if (field.type === 'email' && value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
isValid = false;
message = '请输入有效的邮箱地址';
}
// 手机号验证
if (field.type === 'tel' && value && !/^1[3-9]\d{9}$/.test(value)) {
isValid = false;
message = '请输入有效的手机号码';
}
// 显示验证结果
if (!isValid) {
this.showFieldError(field, message);
} else {
this.showFieldSuccess(field);
}
return isValid;
}
// 显示字段错误
showFieldError(field, message) {
this.clearFieldFeedback(field);
field.classList.add('is-invalid');
const feedback = document.createElement('div');
feedback.className = 'invalid-feedback';
feedback.textContent = message;
field.parentNode.appendChild(feedback);
}
// 显示字段成功
showFieldSuccess(field) {
this.clearFieldFeedback(field);
field.classList.add('is-valid');
}
// 清除字段错误
clearFieldError(field) {
field.classList.remove('is-invalid');
const feedback = field.parentNode.querySelector('.invalid-feedback');
if (feedback) feedback.remove();
}
// 清除字段反馈
clearFieldFeedback(field) {
field.classList.remove('is-invalid', 'is-valid');
const feedback = field.parentNode.querySelector('.invalid-feedback, .valid-feedback');
if (feedback) feedback.remove();
}
// 验证整个表单
validateForm(form) {
let isValid = true;
const fields = form.querySelectorAll('input, textarea, select');
fields.forEach(field => {
if (!this.validateField(field)) {
isValid = false;
}
});
return isValid;
}
// 滚动效果
setupScrollEffects() {
// 导航栏滚动效果
const navbar = document.querySelector('.navbar');
if (navbar) {
window.addEventListener('scroll', () => {
if (window.scrollY > 100) {
navbar.classList.add('navbar-scrolled');
} else {
navbar.classList.remove('navbar-scrolled');
}
});
// 添加滚动样式
const style = document.createElement('style');
style.textContent = `
.navbar-scrolled {
background-color: rgba(255, 255, 255, 0.95) !important;
backdrop-filter: blur(10px);
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
`;
document.head.appendChild(style);
}
// 视差滚动效果
this.setupParallaxEffect();
}
// 视差滚动效果
setupParallaxEffect() {
const parallaxElements = document.querySelectorAll('.hero-section');
window.addEventListener('scroll', () => {
const scrolled = window.pageYOffset;
parallaxElements.forEach(element => {
const rate = scrolled * -0.5;
element.style.transform = `translateY(${rate}px)`;
});
});
}
// 工具提示
setupTooltips() {
// 为所有带有title属性的元素添加工具提示
document.querySelectorAll('[title]').forEach(element => {
element.addEventListener('mouseenter', (e) => {
this.showTooltip(e.target, e.target.getAttribute('title'));
});
element.addEventListener('mouseleave', () => {
this.hideTooltip();
});
});
}
// 显示工具提示
showTooltip(element, text) {
const tooltip = document.createElement('div');
tooltip.className = 'custom-tooltip';
tooltip.textContent = text;
const style = document.createElement('style');
style.textContent = `
.custom-tooltip {
position: absolute;
background: #333;
color: white;
padding: 8px 12px;
border-radius: 4px;
font-size: 14px;
z-index: 1000;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease;
}
.custom-tooltip.show {
opacity: 1;
}
`;
if (!document.querySelector('style[data-tooltip]')) {
style.setAttribute('data-tooltip', 'true');
document.head.appendChild(style);
}
document.body.appendChild(tooltip);
const rect = element.getBoundingClientRect();
tooltip.style.left = rect.left + (rect.width / 2) - (tooltip.offsetWidth / 2) + 'px';
tooltip.style.top = rect.top - tooltip.offsetHeight - 10 + 'px';
setTimeout(() => tooltip.classList.add('show'), 10);
}
// 隐藏工具提示
hideTooltip() {
const tooltip = document.querySelector('.custom-tooltip');
if (tooltip) {
tooltip.classList.remove('show');
setTimeout(() => tooltip.remove(), 300);
}
}
// 通知系统
setupNotifications() {
this.createNotificationContainer();
}
// 创建通知容器
createNotificationContainer() {
const container = document.createElement('div');
container.id = 'notification-container';
const style = document.createElement('style');
style.textContent = `
#notification-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
max-width: 400px;
}
.notification {
background: white;
border-radius: 8px;
padding: 16px;
margin-bottom: 10px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
border-left: 4px solid #007bff;
opacity: 0;
transform: translateX(100%);
transition: all 0.3s ease;
}
.notification.show {
opacity: 1;
transform: translateX(0);
}
.notification.success { border-left-color: #28a745; }
.notification.error { border-left-color: #dc3545; }
.notification.warning { border-left-color: #ffc107; }
.notification.info { border-left-color: #17a2b8; }
`;
document.head.appendChild(style);
document.body.appendChild(container);
}
// 显示通知
showNotification(message, type = 'info', duration = 5000) {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.innerHTML = `
<div class="d-flex justify-content-between align-items-start">
<div>
<strong>${this.getNotificationTitle(type)}</strong>
<div>${message}</div>
</div>
<button type="button" class="btn-close" onclick="this.parentElement.parentElement.remove()"></button>
</div>
`;
document.getElementById('notification-container').appendChild(notification);
setTimeout(() => notification.classList.add('show'), 10);
if (duration > 0) {
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => notification.remove(), 300);
}, duration);
}
}
// 获取通知标题
getNotificationTitle(type) {
const titles = {
success: '成功',
error: '错误',
warning: '警告',
info: '提示'
};
return titles[type] || '通知';
}
// 键盘导航
setupKeyboardNavigation() {
document.addEventListener('keydown', (e) => {
// ESC键关闭模态框
if (e.key === 'Escape') {
const modals = document.querySelectorAll('.modal.show');
modals.forEach(modal => {
const modalInstance = bootstrap.Modal.getInstance(modal);
if (modalInstance) modalInstance.hide();
});
}
// Tab键焦点管理
if (e.key === 'Tab') {
this.manageFocus(e);
}
});
}
// 焦点管理
manageFocus(e) {
const focusableElements = document.querySelectorAll(
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
// 移动端优化
setupMobileOptimizations() {
// 触摸反馈
document.addEventListener('touchstart', (e) => {
if (e.target.matches('button, .btn, .card')) {
e.target.style.transform = 'scale(0.98)';
}
});
document.addEventListener('touchend', (e) => {
if (e.target.matches('button, .btn, .card')) {
setTimeout(() => {
e.target.style.transform = '';
}, 150);
}
});
// 防止双击缩放
let lastTouchEnd = 0;
document.addEventListener('touchend', (e) => {
const now = (new Date()).getTime();
if (now - lastTouchEnd <= 300) {
e.preventDefault();
}
lastTouchEnd = now;
}, false);
// 移动端导航优化
this.setupMobileNavigation();
}
// 移动端导航优化
setupMobileNavigation() {
const navbarToggler = document.querySelector('.navbar-toggler');
const navbarCollapse = document.querySelector('.navbar-collapse');
if (navbarToggler && navbarCollapse) {
// 点击导航链接后自动关闭菜单
navbarCollapse.querySelectorAll('a').forEach(link => {
link.addEventListener('click', () => {
if (window.innerWidth < 992) {
const collapse = new bootstrap.Collapse(navbarCollapse, {
hide: true
});
}
});
});
}
}
// 全局错误处理
setupGlobalErrorHandling() {
window.addEventListener('error', (e) => {
console.error('全局错误:', e.error);
this.showNotification('页面出现错误,请刷新重试', 'error');
});
window.addEventListener('unhandledrejection', (e) => {
console.error('未处理的Promise错误:', e.reason);
this.showNotification('网络请求失败,请检查网络连接', 'error');
});
}
// 性能监控
setupPerformanceMonitoring() {
// 页面加载性能
window.addEventListener('load', () => {
const perfData = performance.getEntriesByType('navigation')[0];
const loadTime = perfData.loadEventEnd - perfData.loadEventStart;
if (loadTime > 3000) {
console.warn('页面加载时间过长:', loadTime + 'ms');
}
});
}
}
// 全局实例
let enhancedInteractionManager;
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
enhancedInteractionManager = new EnhancedInteractionManager();
});
// 导出全局方法供其他脚本使用
window.showNotification = function(message, type, duration) {
if (enhancedInteractionManager) {
enhancedInteractionManager.showNotification(message, type, duration);
}
};
window.showButtonLoading = function(button) {
if (enhancedInteractionManager) {
enhancedInteractionManager.showButtonLoading(button);
}
};