- 更新settings.local.json,移除冗余的权限设置,添加新的Playwright相关权限。 - 删除exhibition_demo_project_2025.md文档,清理不再使用的文件。 - 更新多个HTML页面,统一viewport设置,添加页面加载动画、错误处理和性能优化脚本。 - 统一使用Tailwind CSS的引入方式,提升页面加载性能。 - 增强导航组件,支持移动端菜单和返回顶部功能,改善用户体验。
461 lines
16 KiB
JavaScript
461 lines
16 KiB
JavaScript
// 页面性能优化组件
|
|
(function() {
|
|
let performanceData = {};
|
|
let isOptimizing = false;
|
|
|
|
// 性能监测
|
|
function measurePerformance() {
|
|
if ('performance' in window) {
|
|
const navigation = performance.getEntriesByType('navigation')[0];
|
|
const paint = performance.getEntriesByType('paint');
|
|
|
|
performanceData = {
|
|
// 页面加载时间
|
|
domLoading: navigation.domContentLoadedEventEnd - navigation.navigationStart,
|
|
pageLoading: navigation.loadEventEnd - navigation.navigationStart,
|
|
|
|
// 渲染时间
|
|
firstPaint: paint.find(p => p.name === 'first-paint')?.startTime || 0,
|
|
firstContentfulPaint: paint.find(p => p.name === 'first-contentful-paint')?.startTime || 0,
|
|
|
|
// 网络时间
|
|
dnsTime: navigation.domainLookupEnd - navigation.domainLookupStart,
|
|
connectTime: navigation.connectEnd - navigation.connectStart,
|
|
requestTime: navigation.responseStart - navigation.requestStart,
|
|
downloadTime: navigation.responseEnd - navigation.responseStart,
|
|
|
|
// 内存使用(如果支持)
|
|
memory: performance.memory ? {
|
|
used: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024),
|
|
total: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024),
|
|
limit: Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024)
|
|
} : null
|
|
};
|
|
}
|
|
|
|
return performanceData;
|
|
}
|
|
|
|
// 资源预加载优化
|
|
function optimizeResourceLoading() {
|
|
// 根据当前页面位置调整路径
|
|
const isInPagesFolder = window.location.pathname.includes('/pages/');
|
|
const basePath = isInPagesFolder ? '../' : '';
|
|
|
|
// 预加载关键资源
|
|
const criticalResources = [
|
|
{ href: basePath + 'css/styles.css', as: 'style' },
|
|
{ href: basePath + 'css/animations.css', as: 'style' },
|
|
{ href: basePath + 'js/nav-component.js', as: 'script' }
|
|
];
|
|
|
|
criticalResources.forEach(resource => {
|
|
if (!document.querySelector(`link[href*="${resource.href.split('/').pop()}"]`)) {
|
|
const link = document.createElement('link');
|
|
link.rel = 'preload';
|
|
link.href = resource.href;
|
|
link.as = resource.as;
|
|
document.head.appendChild(link);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 图片懒加载和优化
|
|
function optimizeImages() {
|
|
// 创建 Intersection Observer 用于懒加载
|
|
if ('IntersectionObserver' in window) {
|
|
const imageObserver = new IntersectionObserver((entries, observer) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
const img = entry.target;
|
|
|
|
// 懒加载图片
|
|
if (img.dataset.src) {
|
|
img.src = img.dataset.src;
|
|
img.removeAttribute('data-src');
|
|
}
|
|
|
|
// 添加加载状态
|
|
img.classList.add('loading');
|
|
|
|
img.addEventListener('load', () => {
|
|
img.classList.remove('loading');
|
|
img.classList.add('loaded');
|
|
});
|
|
|
|
img.addEventListener('error', () => {
|
|
img.classList.add('error');
|
|
img.style.display = 'none';
|
|
});
|
|
|
|
observer.unobserve(img);
|
|
}
|
|
});
|
|
}, {
|
|
rootMargin: '50px',
|
|
threshold: 0.01
|
|
});
|
|
|
|
// 观察所有图片
|
|
document.querySelectorAll('img[data-src], img:not([src])').forEach(img => {
|
|
imageObserver.observe(img);
|
|
});
|
|
}
|
|
|
|
// 为现有图片添加优化
|
|
document.querySelectorAll('img').forEach(img => {
|
|
// 添加 loading="lazy" 属性
|
|
if (!img.loading) {
|
|
img.loading = 'lazy';
|
|
}
|
|
|
|
// 添加 decode="async" 属性
|
|
if (!img.decode) {
|
|
img.decode = 'async';
|
|
}
|
|
|
|
// 图片错误处理
|
|
img.addEventListener('error', function() {
|
|
this.style.opacity = '0';
|
|
this.style.transition = 'opacity 0.3s';
|
|
});
|
|
});
|
|
}
|
|
|
|
// CSS 优化
|
|
function optimizeCSS() {
|
|
// 内联关键 CSS
|
|
const criticalCSS = `
|
|
/* 关键渲染路径 CSS */
|
|
body { font-family: 'Inter', sans-serif; margin: 0; padding: 0; }
|
|
#navbar { position: fixed; top: 0; width: 100%; z-index: 50; background: white; }
|
|
.container { max-width: 1200px; margin: 0 auto; padding: 0 1rem; }
|
|
|
|
/* 页面加载器样式 */
|
|
#page-loader { position: fixed; inset: 0; z-index: 9999; background: white; display: flex; align-items: center; justify-content: center; }
|
|
`;
|
|
|
|
// 添加关键 CSS 到 head
|
|
if (!document.getElementById('critical-css')) {
|
|
const style = document.createElement('style');
|
|
style.id = 'critical-css';
|
|
style.textContent = criticalCSS;
|
|
document.head.insertBefore(style, document.head.firstChild);
|
|
}
|
|
|
|
// 异步加载非关键 CSS
|
|
const nonCriticalCSS = document.querySelectorAll('link[rel="stylesheet"]:not([data-critical])');
|
|
nonCriticalCSS.forEach(link => {
|
|
if (link.href.includes('tailwind') || link.href.includes('font-awesome')) {
|
|
link.media = 'print';
|
|
link.addEventListener('load', function() {
|
|
this.media = 'all';
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// JavaScript 优化
|
|
function optimizeJavaScript() {
|
|
// 延迟加载非关键脚本
|
|
const deferScripts = [
|
|
'js/back-to-top.js',
|
|
'js/mobile-optimize.js'
|
|
];
|
|
|
|
deferScripts.forEach(src => {
|
|
const script = document.querySelector(`script[src*="${src}"]`);
|
|
if (script && !script.defer && !script.async) {
|
|
script.defer = true;
|
|
}
|
|
});
|
|
|
|
// 移除未使用的事件监听器(防止内存泄漏)
|
|
const cleanupEvents = () => {
|
|
// 移除已销毁元素的事件监听器
|
|
document.querySelectorAll('[data-cleanup]').forEach(el => {
|
|
el.removeEventListener('click', null);
|
|
el.removeEventListener('scroll', null);
|
|
el.removeEventListener('resize', null);
|
|
});
|
|
};
|
|
|
|
// 页面卸载时清理
|
|
window.addEventListener('beforeunload', cleanupEvents);
|
|
}
|
|
|
|
// 网络优化
|
|
function optimizeNetwork() {
|
|
// DNS 预解析
|
|
const domains = [
|
|
'fonts.googleapis.com',
|
|
'cdnjs.cloudflare.com',
|
|
'cdn.jsdelivr.net'
|
|
];
|
|
|
|
domains.forEach(domain => {
|
|
if (!document.querySelector(`link[rel="dns-prefetch"][href*="${domain}"]`)) {
|
|
const link = document.createElement('link');
|
|
link.rel = 'dns-prefetch';
|
|
link.href = `https://${domain}`;
|
|
document.head.appendChild(link);
|
|
}
|
|
});
|
|
|
|
// 预连接到关键域名
|
|
const preconnectDomains = ['fonts.gstatic.com'];
|
|
preconnectDomains.forEach(domain => {
|
|
if (!document.querySelector(`link[rel="preconnect"][href*="${domain}"]`)) {
|
|
const link = document.createElement('link');
|
|
link.rel = 'preconnect';
|
|
link.href = `https://${domain}`;
|
|
link.crossOrigin = '';
|
|
document.head.appendChild(link);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 缓存优化
|
|
function optimizeCache() {
|
|
// Service Worker 注册(如果支持)
|
|
if ('serviceWorker' in navigator) {
|
|
window.addEventListener('load', () => {
|
|
navigator.serviceWorker.register('./sw.js')
|
|
.then(registration => {
|
|
console.log('SW registered: ', registration);
|
|
})
|
|
.catch(registrationError => {
|
|
console.log('SW registration failed: ', registrationError);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 本地存储优化
|
|
try {
|
|
// 缓存静态数据
|
|
if (localStorage) {
|
|
const cacheKey = 'page-cache-' + window.location.pathname;
|
|
const cacheTime = 30 * 60 * 1000; // 30分钟
|
|
|
|
const cachedData = localStorage.getItem(cacheKey);
|
|
if (cachedData) {
|
|
const { timestamp, data } = JSON.parse(cachedData);
|
|
if (Date.now() - timestamp < cacheTime) {
|
|
// 使用缓存数据
|
|
console.log('Using cached data');
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn('LocalStorage not available');
|
|
}
|
|
}
|
|
|
|
// 动画性能优化
|
|
function optimizeAnimations() {
|
|
// 检测是否需要减少动画
|
|
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
|
|
if (prefersReducedMotion) {
|
|
document.body.classList.add('reduce-motion');
|
|
|
|
// 添加减少动画的 CSS
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.reduce-motion *,
|
|
.reduce-motion *::before,
|
|
.reduce-motion *::after {
|
|
animation-duration: 0.01ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.01ms !important;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
// 使用 transform 和 opacity 进行动画(硬件加速)
|
|
document.querySelectorAll('[class*="animate-"]').forEach(el => {
|
|
el.style.willChange = 'transform, opacity';
|
|
|
|
// 动画完成后移除 will-change
|
|
el.addEventListener('animationend', function() {
|
|
this.style.willChange = 'auto';
|
|
}, { once: true });
|
|
});
|
|
}
|
|
|
|
// 内存优化
|
|
function optimizeMemory() {
|
|
// 定期清理无用的 DOM 元素
|
|
const cleanup = () => {
|
|
// 移除隐藏的元素(如果不再需要)
|
|
document.querySelectorAll('.hidden, [style*="display: none"]').forEach(el => {
|
|
if (el.dataset.keepHidden !== 'true') {
|
|
// 这里可以添加更智能的清理逻辑
|
|
}
|
|
});
|
|
};
|
|
|
|
// 每5分钟执行一次清理
|
|
setInterval(cleanup, 5 * 60 * 1000);
|
|
|
|
// 监听内存压力(如果支持)
|
|
if ('memory' in performance) {
|
|
const checkMemory = () => {
|
|
const used = performance.memory.usedJSHeapSize;
|
|
const limit = performance.memory.jsHeapSizeLimit;
|
|
const usage = used / limit;
|
|
|
|
if (usage > 0.8) {
|
|
console.warn('High memory usage detected:', Math.round(usage * 100) + '%');
|
|
cleanup();
|
|
}
|
|
};
|
|
|
|
setInterval(checkMemory, 30000); // 每30秒检查一次
|
|
}
|
|
}
|
|
|
|
// 性能监控和报告
|
|
function monitorPerformance() {
|
|
// 使用 Performance Observer 监控
|
|
if ('PerformanceObserver' in window) {
|
|
// 监控 Long Tasks
|
|
const longTaskObserver = new PerformanceObserver(list => {
|
|
list.getEntries().forEach(entry => {
|
|
if (entry.duration > 50) {
|
|
console.warn('Long task detected:', entry.duration + 'ms');
|
|
}
|
|
});
|
|
});
|
|
|
|
try {
|
|
longTaskObserver.observe({ entryTypes: ['longtask'] });
|
|
} catch (e) {
|
|
// longtask 不被支持
|
|
}
|
|
|
|
// 监控 Layout Shift
|
|
const clsObserver = new PerformanceObserver(list => {
|
|
list.getEntries().forEach(entry => {
|
|
if (entry.value > 0.1) {
|
|
console.warn('Layout shift detected:', entry.value);
|
|
}
|
|
});
|
|
});
|
|
|
|
try {
|
|
clsObserver.observe({ entryTypes: ['layout-shift'] });
|
|
} catch (e) {
|
|
// layout-shift 不被支持
|
|
}
|
|
}
|
|
}
|
|
|
|
// 添加性能优化样式
|
|
function addPerformanceStyles() {
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
/* 硬件加速 */
|
|
.gpu-accelerated {
|
|
transform: translateZ(0);
|
|
will-change: transform, opacity;
|
|
}
|
|
|
|
/* 图片加载状态 */
|
|
img.loading {
|
|
opacity: 0.3;
|
|
filter: blur(5px);
|
|
transition: opacity 0.3s, filter 0.3s;
|
|
}
|
|
|
|
img.loaded {
|
|
opacity: 1;
|
|
filter: none;
|
|
}
|
|
|
|
img.error {
|
|
opacity: 0;
|
|
display: none !important;
|
|
}
|
|
|
|
/* 字体加载优化 */
|
|
.font-loading {
|
|
font-display: swap;
|
|
}
|
|
|
|
/* 减少重绘 */
|
|
.contain-layout {
|
|
contain: layout;
|
|
}
|
|
|
|
.contain-paint {
|
|
contain: paint;
|
|
}
|
|
|
|
/* 滚动优化 */
|
|
.scroll-smooth {
|
|
scroll-behavior: smooth;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
// 初始化性能优化
|
|
function init() {
|
|
if (isOptimizing) return;
|
|
isOptimizing = true;
|
|
|
|
// 立即执行的优化
|
|
optimizeResourceLoading();
|
|
optimizeCSS();
|
|
optimizeNetwork();
|
|
addPerformanceStyles();
|
|
|
|
// DOM 加载完成后执行
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
optimizeImages();
|
|
optimizeJavaScript();
|
|
optimizeAnimations();
|
|
monitorPerformance();
|
|
});
|
|
} else {
|
|
optimizeImages();
|
|
optimizeJavaScript();
|
|
optimizeAnimations();
|
|
monitorPerformance();
|
|
}
|
|
|
|
// 页面完全加载后执行
|
|
window.addEventListener('load', () => {
|
|
optimizeCache();
|
|
optimizeMemory();
|
|
|
|
// 测量性能
|
|
setTimeout(() => {
|
|
const perf = measurePerformance();
|
|
console.log('Performance metrics:', perf);
|
|
|
|
// 发送性能数据到分析系统(如果需要)
|
|
if (window.gtag) {
|
|
gtag('event', 'performance', {
|
|
'page_load_time': Math.round(perf.pageLoading),
|
|
'dom_load_time': Math.round(perf.domLoading)
|
|
});
|
|
}
|
|
}, 1000);
|
|
});
|
|
}
|
|
|
|
// 提供外部接口
|
|
window.PerformanceOptimizer = {
|
|
measurePerformance,
|
|
optimizeImages,
|
|
getPerformanceData: () => performanceData
|
|
};
|
|
|
|
// 初始化
|
|
init();
|
|
})(); |