优化项目配置和页面结构
- 更新settings.local.json,移除冗余的权限设置,添加新的Playwright相关权限。 - 删除exhibition_demo_project_2025.md文档,清理不再使用的文件。 - 更新多个HTML页面,统一viewport设置,添加页面加载动画、错误处理和性能优化脚本。 - 统一使用Tailwind CSS的引入方式,提升页面加载性能。 - 增强导航组件,支持移动端菜单和返回顶部功能,改善用户体验。
This commit is contained in:
461
web_frontend/web_result/js/performance-optimizer.js
Normal file
461
web_frontend/web_result/js/performance-optimizer.js
Normal file
@@ -0,0 +1,461 @@
|
||||
// 页面性能优化组件
|
||||
(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();
|
||||
})();
|
||||
Reference in New Issue
Block a user