有一个需求是批量生成仿真数据在3D地图上面,每条检测线估计40台车在循环不停的行驶,中间会有线路两侧的摄像头告警,和不同速度的车辆即将碰撞的告警。数据量大的话会出现卡顿现象。(在电脑大屏测试没问题,在风控机会出现卡顿)
我整理了以下几种解决方案,因为3D园区地图页面是通过ifrme引入的。
1、兜底方案每天固定时间进行重载,通过一个设置弹窗,里面有24个小时,如果相关人员上班的时候可以设置几个时间点进行手动重载;如果是简单的可以内置代码重置每天凌晨进行重载
2、监听内存,进行边界预警,通过performance.memory监听使用内存和总内存,进行定时器间隔对比(数据密集型15s,普通大屏60s)
3、其他图片大小和接口次数调用等,限制重载次数和用户友好提示等
主页面代码
// ============ 放在组件顶部,替换原有配置 ============
// 内存配置
const MEMORY_CONFIG = {
// 主页面阈值
MAIN: {
RELOAD_THRESHOLD: 6 * 1024 * 1024 * 1024, // 6GB 触发重载
EMERGENCY_THRESHOLD: 7.5 * 1024 * 1024 * 1024, // 7.5GB 紧急重载
RATIO_THRESHOLD: 0.70, // 70%占比触发
},
// iframe阈值(3D场景通常内存占用大)
IFRAME: {
RELOAD_THRESHOLD: 2.5 * 1024 * 1024 * 1024, // 2.5GB 触发iframe重载
EMERGENCY_THRESHOLD: 3.5 * 1024 * 1024 * 1024, // 3.5GB 紧急重载
},
// 全局配置
CHECK_INTERVAL: 8000, // 8秒检查一次(避免频繁检查影响性能)
COOLDOWN: 180000, // 3分钟冷却
MAX_RELOAD: 3, // 最大重载次数
IFRAME_RELOAD_INTERVAL: 3600000, // iframe每小时重载一次(防止WebGL泄漏)
};
// 状态管理
let memoryCheckTimer: number | null = null;
let lastReloadTime = 0;
let reloadCount = 0;
let isReloading = false;
let iframeMemoryData = {
usedHeap: 0,
totalHeap: 0,
lastUpdate: 0
};
// ============ 内存监控主函数 ============
function startMemoryMonitor() {
stopMemoryMonitor();
// 检查主页面内存API
if (!window.performance || !(window.performance as any).memory) {
console.warn('⚠️ 主页面不支持 performance.memory API,使用降级策略');
startFallbackMonitor();
return;
}
console.log('✅ 内存监控已启动', {
主页阈值: `${(MEMORY_CONFIG.MAIN.RELOAD_THRESHOLD / 1024 / 1024 / 1024).toFixed(1)}GB`,
iframe阈值: `${(MEMORY_CONFIG.IFRAME.RELOAD_THRESHOLD / 1024 / 1024 / 1024).toFixed(1)}GB`,
检查间隔: `${MEMORY_CONFIG.CHECK_INTERVAL / 1000}秒`
});
// 监听iframe消息
window.addEventListener('message', handleIframeMemoryMessage);
// 立即检查一次
checkMemoryAndReload();
// 定时检查
memoryCheckTimer = window.setInterval(() => {
checkMemoryAndReload();
}, MEMORY_CONFIG.CHECK_INTERVAL);
}
// ============ 处理iframe内存消息 ============
function handleIframeMemoryMessage(event: MessageEvent) {
try {
const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
// 只处理内存数据消息
if (data && data.type === 'iframe_memory') {
iframeMemoryData.usedHeap = data.usedHeapSize || 0;
iframeMemoryData.totalHeap = data.totalHeapSize || 0;
iframeMemoryData.lastUpdate = Date.now();
// 检查iframe内存是否超限
checkIframeMemory();
}
} catch (error) {
// 忽略非JSON消息
}
}
// ============ 检查iframe内存 ============
function checkIframeMemory() {
if (isReloading) return;
const usedGB = iframeMemoryData.usedHeap / 1024 / 1024 / 1024;
// iframe内存超过紧急阈值
if (iframeMemoryData.usedHeap > MEMORY_CONFIG.IFRAME.EMERGENCY_THRESHOLD) {
console.error(`🚨 iframe内存紧急: ${usedGB.toFixed(2)}GB,立即重载iframe`);
reloadIframeOnly('emergency');
return;
}
// iframe内存超过重载阈值
if (iframeMemoryData.usedHeap > MEMORY_CONFIG.IFRAME.RELOAD_THRESHOLD) {
console.warn(`⚠️ iframe内存过高: ${usedGB.toFixed(2)}GB,准备重载iframe`);
reloadIframeOnly('threshold');
}
}
// ============ 核心检查函数(主页面) ============
function checkMemoryAndReload() {
if (isReloading) return;
try {
const memory = (window.performance as any).memory;
// 主页面内存
const mainUsed = memory.usedJSHeapSize;
const mainTotal = memory.jsHeapSizeLimit;
const mainRatio = mainUsed / mainTotal;
// 总内存 = 主页面 + iframe
const totalUsed = mainUsed + (iframeMemoryData.usedHeap || 0);
const totalGB = (totalUsed / 1024 / 1024 / 1024).toFixed(2);
const mainGB = (mainUsed / 1024 / 1024 / 1024).toFixed(2);
const iframeGB = ((iframeMemoryData.usedHeap || 0) / 1024 / 1024 / 1024).toFixed(2);
// 打印内存状态(降低频率,只在变化大时打印)
if (Math.random() < 0.1) { // 10%概率打印,减少日志
console.log(`📊 内存: 主页=${mainGB}GB, iframe=${iframeGB}GB, 总计=${totalGB}GB`);
}
// ===== 分级预警 =====
// 1. 紧急阈值 - 立即重载整个页面
if (mainUsed > MEMORY_CONFIG.MAIN.EMERGENCY_THRESHOLD ||
totalUsed > (MEMORY_CONFIG.MAIN.EMERGENCY_THRESHOLD + MEMORY_CONFIG.IFRAME.EMERGENCY_THRESHOLD)) {
console.error(`🚨 紧急内存超限: ${totalGB}GB,立即全页重载!`);
triggerFullReload('emergency');
return;
}
// 2. 主页面占比阈值
if (mainRatio > MEMORY_CONFIG.MAIN.RATIO_THRESHOLD) {
console.warn(`⚠️ 主页面内存占比过高: ${(mainRatio * 100).toFixed(1)}%`);
// 检查冷却
const now = Date.now();
if (now - lastReloadTime < MEMORY_CONFIG.COOLDOWN) {
console.log(`⏳ 冷却中,尝试清理内存`);
attemptMemoryCleanup();
return;
}
if (reloadCount >= MEMORY_CONFIG.MAX_RELOAD) {
console.error(`🚫 达到最大重载次数,发送告警`);
sendAdminAlert('main_memory_high');
return;
}
triggerFullReload('threshold');
return;
}
// 3. 主页面绝对阈值
if (mainUsed > MEMORY_CONFIG.MAIN.RELOAD_THRESHOLD) {
console.warn(`⚠️ 主页面内存超过阈值: ${mainGB}GB`);
const now = Date.now();
if (now - lastReloadTime < MEMORY_CONFIG.COOLDOWN) {
attemptMemoryCleanup();
return;
}
if (reloadCount >= MEMORY_CONFIG.MAX_RELOAD) {
sendAdminAlert('main_memory_threshold');
return;
}
triggerFullReload('threshold');
}
// 4. 定期清理iframe(防止WebGL泄漏)
if (iframeMemoryData.lastUpdate > 0) {
const timeSinceIframeUpdate = Date.now() - iframeMemoryData.lastUpdate;
if (timeSinceIframeUpdate > MEMORY_CONFIG.IFRAME_RELOAD_INTERVAL) {
console.log('🔄 iframe长时间未更新内存,主动重载');
reloadIframeOnly('maintenance');
}
}
} catch (error) {
console.error('❌ 内存检查异常:', error);
}
}
// ============ 只重载iframe ============
function reloadIframeOnly(reason: string) {
if (isReloading) return;
isReloading = true;
console.log(`🔄 重载iframe (原因: ${reason})`);
try {
const iframe = threeDRef.value as HTMLIFrameElement;
if (iframe) {
// 显示通知
showIframeReloadNotification(reason);
// 延迟重载
setTimeout(() => {
// 先清空src
iframe.src = 'about:blank';
// 等待一帧后再恢复
requestAnimationFrame(() => {
setTimeout(() => {
iframe.src = threeDUrl.value;
// 重置iframe内存数据
iframeMemoryData.usedHeap = 0;
iframeMemoryData.lastUpdate = 0;
console.log('✅ iframe重载完成');
isReloading = false;
}, 100);
});
}, 1000);
} else {
isReloading = false;
}
} catch (error) {
console.error('❌ iframe重载失败:', error);
isReloading = false;
// 如果iframe重载失败,尝试全页重载
triggerFullReload('iframe_reload_failed');
}
}
// ============ 全页重载 ============
function triggerFullReload(reason: string) {
if (isReloading) return;
isReloading = true;
const now = Date.now();
lastReloadTime = now;
reloadCount++;
console.log(`🔄 全页重载 (原因: ${reason}, 第 ${reloadCount} 次)`);
// 保存状态
try {
saveDashboardState();
} catch (e) {
console.warn('保存状态失败:', e);
}
// 显示全屏通知
showFullReloadNotification(reason);
// 延迟重载
setTimeout(() => {
try {
// 发送消息给iframe准备重载
const iframe = threeDRef.value as HTMLIFrameElement;
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage({ type: 'page_reloading' }, '*');
}
} catch (e) {
// ignore
}
// 执行重载
setTimeout(() => {
try {
window.location.reload();
} catch (e) {
try {
window.location.href = window.location.href;
} catch (e2) {
history.go(0);
}
}
}, 500);
}, 2000);
}
// ============ 清理内存(不重载) ============
function attemptMemoryCleanup() {
console.log('🧹 尝试清理内存...');
try {
// 1. 清理主页面图表
if (window.charts && window.charts.length > 0) {
window.charts.forEach((chart: any) => {
if (chart && typeof chart.dispose === 'function') {
chart.dispose();
}
});
window.charts = [];
console.log('✅ 已清理图表');
}
// 2. 清理数据缓存
if (warnings.value.length > 50) {
warnings.value = warnings.value.slice(0, 30);
}
if (historyList.value.length > 50) {
historyList.value = historyList.value.slice(0, 30);
}
// 3. 通知iframe清理(发送清理消息)
try {
const iframe = threeDRef.value as HTMLIFrameElement;
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage({
type: 'cleanup_memory',
timestamp: Date.now()
}, '*');
console.log('✅ 已发送清理消息到iframe');
}
} catch (e) {
// ignore
}
// 4. 触发GC
if ((window as any).gc) {
try { (window as any).gc(); } catch (e) {}
}
console.log('✅ 内存清理完成');
} catch (e) {
console.warn('内存清理失败:', e);
}
}
// ============ 显示iframe重载通知 ============
function showIframeReloadNotification(reason: string) {
const notification = document.createElement('div');
notification.id = 'iframe-reload-notification';
notification.style.cssText = `
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 165, 0, 0.9);
color: white;
padding: 12px 24px;
border-radius: 8px;
z-index: 99999;
font-size: 14px;
font-family: 'Microsoft YaHei', sans-serif;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
animation: slideUp 0.3s ease;
`;
notification.innerHTML = `
<span>🔄 3D场景内存过高,正在重新加载...</span>
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transition = 'opacity 0.5s';
setTimeout(() => notification.remove(), 500);
}, 3000);
}
// ============ 显示全页重载通知 ============
function showFullReloadNotification(reason: string) {
const existing = document.getElementById('full-reload-notification');
if (existing) existing.remove();
const reasons: Record<string, string> = {
'emergency': '内存严重超限',
'threshold': '内存超过阈值',
'fps': '页面卡顿严重',
'schedule': '定时刷新',
'iframe_reload_failed': '3D场景加载失败'
};
const notification = document.createElement('div');
notification.id = 'full-reload-notification';
notification.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: center;
z-index: 999999;
font-family: 'Microsoft YaHei', sans-serif;
`;
notification.innerHTML = `
<div style="
background: linear-gradient(135deg, #1a1a2e, #16213e);
padding: 50px 60px;
border-radius: 20px;
text-align: center;
border: 2px solid #ff6b6b;
box-shadow: 0 0 60px rgba(255, 0, 0, 0.2);
max-width: 500px;
">
<div style="font-size: 56px; margin-bottom: 16px;">🔄</div>
<h2 style="color: #ff6b6b; margin: 0 0 8px; font-size: 22px;">系统正在刷新</h2>
<p style="color: #aaa; margin: 0 0 4px; font-size: 14px;">
原因: ${reasons[reason] || reason}
</p>
<p style="color: #666; margin: 0 0 24px; font-size: 13px;">
第 ${reloadCount} 次重载
</p>
<div style="
width: 100%;
height: 4px;
background: #333;
border-radius: 2px;
overflow: hidden;
">
<div style="
width: 100%;
height: 100%;
background: linear-gradient(90deg, #ff6b6b, #ff4444);
animation: reloadProgress 2s ease-in-out forwards;
"></div>
</div>
<p style="color: #555; margin-top: 16px; font-size: 12px;">
请稍候,页面将自动恢复...
</p>
</div>
`;
// 添加动画样式
if (!document.getElementById('reload-animation-style')) {
const style = document.createElement('style');
style.id = 'reload-animation-style';
style.textContent = `
@keyframes reloadProgress {
0% { width: 0%; }
100% { width: 100%; }
}
@keyframes slideUp {
from { transform: translateX(-50%) translateY(20px); opacity: 0; }
to { transform: translateX(-50%) translateY(0); opacity: 1; }
}
`;
document.head.appendChild(style);
}
document.body.appendChild(notification);
}
// ============ 状态保存 ============
function saveDashboardState() {
try {
const state = {
timestamp: Date.now(),
warnings: warnings.value.slice(0, 20),
history: historyList.value.slice(0, 20),
scheduleTimes: selectedTimes.value,
// 保存iframe内存数据
iframeMemory: iframeMemoryData
};
localStorage.setItem('dashboard_reload_backup', JSON.stringify(state));
} catch (e) {
// ignore
}
}
// ============ 管理员告警 ============
function sendAdminAlert(type: string) {
try {
const data = {
type: 'dashboard_memory_critical',
subType: type,
reloadCount: reloadCount,
timestamp: Date.now(),
url: window.location.href,
memory: {
main: (window.performance as any).memory ? {
used: (window.performance as any).memory.usedJSHeapSize,
total: (window.performance as any).memory.jsHeapSizeLimit
} : null,
iframe: iframeMemoryData
}
};
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/monitor/alert', JSON.stringify(data));
}
} catch (e) {
// ignore
}
}
// ============ 备用监控策略 ============
function startFallbackMonitor() {
console.log('⚠️ 使用备用监控策略');
let checkCount = 0;
let lastCheckTime = Date.now();
memoryCheckTimer = window.setInterval(() => {
checkCount++;
const now = Date.now();
// 检测主页面响应
try {
const start = performance.now();
document.body.getBoundingClientRect();
const end = performance.now();
if (end - start > 200) {
console.warn(`⚠️ 页面响应缓慢: ${(end - start).toFixed(0)}ms`);
if (end - start > 500) {
console.error('🚨 页面无响应,触发重载');
triggerFullReload('fallback');
}
}
} catch (e) {
console.error('❌ 页面崩溃,触发重载');
triggerFullReload('crash');
}
// 每30分钟检查iframe是否存活
if (checkCount % 60 === 0) { // 60 * 30秒 = 30分钟
checkIframeAlive();
}
lastCheckTime = now;
// 每2小时强制全页重载
if (checkCount > 240) {
console.log('🔄 备用策略: 定时全页重载');
triggerFullReload('schedule');
checkCount = 0;
}
}, 30000);
}
// ============ 检查iframe是否存活 ============
function checkIframeAlive() {
try {
const iframe = threeDRef.value as HTMLIFrameElement;
if (iframe && iframe.contentWindow) {
// 发送ping消息
iframe.contentWindow.postMessage({ type: 'ping', timestamp: Date.now() }, '*');
// 设置超时检测
let pingTimeout = setTimeout(() => {
console.warn('⚠️ iframe无响应,尝试重载');
reloadIframeOnly('timeout');
}, 5000);
// 监听pong响应
const pongHandler = (event: MessageEvent) => {
try {
const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
if (data && data.type === 'pong') {
clearTimeout(pingTimeout);
window.removeEventListener('message', pongHandler);
}
} catch (e) {
// ignore
}
};
window.addEventListener('message', pongHandler, { once: true });
}
} catch (e) {
console.warn('检查iframe存活失败:', e);
}
}
// ============ 停止监控 ============
function stopMemoryMonitor() {
if (memoryCheckTimer) {
clearInterval(memoryCheckTimer);
memoryCheckTimer = null;
}
window.removeEventListener('message', handleIframeMemoryMessage);
// 移除通知
document.querySelectorAll('[id$="-reload-notification"]').forEach(el => el.remove());
}
iframe内部代码,把页面内存传递给主页
// ============ 放在iframe页面中 ============
// 监听父页面消息
window.addEventListener(‘message’, function(event) {
try {
const data = typeof event.data === ‘string’ ? JSON.parse(event.data) : event.data;
switch (data.type) {
case 'ping':
// 响应ping
event.source.postMessage({ type: 'pong', timestamp: Date.now() }, '*');
break;
case 'cleanup_memory':
// 清理3D场景内存
cleanupThreeScene();
break;
case 'page_reloading':
// 父页面即将重载,保存状态
saveThreeState();
break;
}
} catch (e) {
// ignore
}
});
// 定时报告内存使用(如果支持)
function reportMemoryUsage() {
if (window.performance && (window.performance as any).memory) {
const memory = (window.performance as any).memory;
window.parent.postMessage({
type: ‘iframe_memory’,
usedHeapSize: memory.usedJSHeapSize,
totalHeapSize: memory.jsHeapSizeLimit,
timestamp: Date.now()
}, ‘*’);
}
}
// 每10秒报告一次内存
setInterval(reportMemoryUsage, 10000);
// 清理3D场景
function cleanupThreeScene() {
console.log(‘🧹 清理3D场景内存’);
try {
// 如果使用Three.js
if (window.renderer) {
window.renderer.dispose();
}
if (window.scene) {
window.scene.traverse((child: any) => {
if (child.geometry) {
child.geometry.dispose();
}
if (child.material) {
if (Array.isArray(child.material)) {
child.material.forEach(m => m.dispose());
} else {
child.material.dispose();
}
}
});
}
// 触发GC
if ((window as any).gc) {
try { (window as any).gc(); } catch (e) {}
}
console.log('✅ 3D场景清理完成');
} catch (e) {
console.warn(‘清理3D场景失败:’, e);
}
}
// 保存3D状态
function saveThreeState() {
try {
const state = {
timestamp: Date.now(),
// 保存相机位置等
camera: window.camera ? {
position: window.camera.position.toArray(),
target: window.camera.target ? window.camera.target.toArray() : null
} : null
};
localStorage.setItem(‘three_scene_backup’, JSON.stringify(state));
} catch (e) {
// ignore
}
}
关键优化点总结
| 优化项 | 作用 | 效果 |
|---|---|---|
| 双内存监控 | 同时监控主页面和iframe内存 | 提前发现内存问题 |
| 分级预警 | 阈值/占比/紧急三级预警 | 避免浏览器崩溃 |
| iframe独立重载 | 只重载3D场景,不刷新整个页面 | 提升用户体验 |
| 定期清理 | 每小时重载iframe | 防止WebGL内存泄漏 |
| 内存消息通信 | 主页面与iframe双向通信 | 实时监控iframe内存 |
| 加载状态 | 显示iframe加载状态 | 改善用户体验 |
| 状态保存 | 重载前保存关键状态 | 数据不丢失 |
