Optimize frontend rendering: delta updates, rAF debouncing, cached DOM refs
- Disable Chart.js animations on real-time FPS and perf charts - Dashboard: delta-update profile badges on state changes instead of full DOM rebuild - Dashboard: cache querySelector results in Map for metrics update loop - Dashboard: debounce poll interval slider restart (300ms) - Calibration: debounce ResizeObserver and span drag via requestAnimationFrame - Calibration: batch updateCalibrationPreview canvas render into rAF Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,9 @@ class CalibrationModal extends Modal {
|
||||
|
||||
const calibModal = new CalibrationModal();
|
||||
|
||||
let _dragRaf = null;
|
||||
let _previewRaf = null;
|
||||
|
||||
/* ── Public API (exported names unchanged) ────────────────────── */
|
||||
|
||||
export async function showCalibration(deviceId) {
|
||||
@@ -117,8 +120,12 @@ export async function showCalibration(deviceId) {
|
||||
|
||||
if (!window._calibrationResizeObserver) {
|
||||
window._calibrationResizeObserver = new ResizeObserver(() => {
|
||||
updateSpanBars();
|
||||
renderCalibrationCanvas();
|
||||
if (window._calibrationResizeRaf) return;
|
||||
window._calibrationResizeRaf = requestAnimationFrame(() => {
|
||||
window._calibrationResizeRaf = null;
|
||||
updateSpanBars();
|
||||
renderCalibrationCanvas();
|
||||
});
|
||||
});
|
||||
}
|
||||
window._calibrationResizeObserver.observe(preview);
|
||||
@@ -202,8 +209,12 @@ export function updateCalibrationPreview() {
|
||||
if (toggleEl) toggleEl.classList.toggle('edge-disabled', count === 0);
|
||||
});
|
||||
|
||||
updateSpanBars();
|
||||
renderCalibrationCanvas();
|
||||
if (_previewRaf) cancelAnimationFrame(_previewRaf);
|
||||
_previewRaf = requestAnimationFrame(() => {
|
||||
_previewRaf = null;
|
||||
updateSpanBars();
|
||||
renderCalibrationCanvas();
|
||||
});
|
||||
}
|
||||
|
||||
export function renderCalibrationCanvas() {
|
||||
@@ -509,11 +520,19 @@ function initSpanDrag() {
|
||||
if (handleType === 'start') span.start = Math.min(fraction, span.end - MIN_SPAN);
|
||||
else span.end = Math.max(fraction, span.start + MIN_SPAN);
|
||||
|
||||
updateSpanBars();
|
||||
renderCalibrationCanvas();
|
||||
if (!_dragRaf) {
|
||||
_dragRaf = requestAnimationFrame(() => {
|
||||
_dragRaf = null;
|
||||
updateSpanBars();
|
||||
renderCalibrationCanvas();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
if (_dragRaf) { cancelAnimationFrame(_dragRaf); _dragRaf = null; }
|
||||
updateSpanBars();
|
||||
renderCalibrationCanvas();
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
}
|
||||
@@ -548,11 +567,19 @@ function initSpanDrag() {
|
||||
span.start = newStart;
|
||||
span.end = newStart + spanWidth;
|
||||
|
||||
updateSpanBars();
|
||||
renderCalibrationCanvas();
|
||||
if (!_dragRaf) {
|
||||
_dragRaf = requestAnimationFrame(() => {
|
||||
_dragRaf = null;
|
||||
updateSpanBars();
|
||||
renderCalibrationCanvas();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
if (_dragRaf) { cancelAnimationFrame(_dragRaf); _dragRaf = null; }
|
||||
updateSpanBars();
|
||||
renderCalibrationCanvas();
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ let _fpsCharts = {}; // { targetId: Chart }
|
||||
let _lastRunningIds = []; // sorted target IDs from previous render
|
||||
let _uptimeBase = {}; // { targetId: { seconds, timestamp } }
|
||||
let _uptimeTimer = null;
|
||||
let _metricsElements = new Map();
|
||||
|
||||
function _loadFpsHistory() {
|
||||
try {
|
||||
@@ -99,7 +100,7 @@ function _createFpsChart(canvasId, history, fpsTarget) {
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: true,
|
||||
animation: false,
|
||||
plugins: { legend: { display: false }, tooltip: { enabled: false } },
|
||||
scales: {
|
||||
x: { display: false },
|
||||
@@ -124,6 +125,18 @@ function _initFpsCharts(runningTargetIds) {
|
||||
_fpsCharts[id] = _createFpsChart(`dashboard-fps-${id}`, history, fpsTarget);
|
||||
}
|
||||
_saveFpsHistory();
|
||||
_cacheMetricsElements(runningTargetIds);
|
||||
}
|
||||
|
||||
function _cacheMetricsElements(runningIds) {
|
||||
_metricsElements.clear();
|
||||
for (const id of runningIds) {
|
||||
_metricsElements.set(id, {
|
||||
fps: document.querySelector(`[data-fps-text="${id}"]`),
|
||||
errors: document.querySelector(`[data-errors-text="${id}"]`),
|
||||
row: document.querySelector(`[data-target-id="${id}"]`),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Update running target metrics in-place (no HTML rebuild). */
|
||||
@@ -152,17 +165,18 @@ function _updateRunningMetrics(enrichedRunning) {
|
||||
_setUptimeBase(target.id, metrics.uptime_seconds);
|
||||
}
|
||||
|
||||
// Update text values
|
||||
const fpsEl = document.querySelector(`[data-fps-text="${target.id}"]`);
|
||||
// Update text values (use cached refs, fallback to querySelector)
|
||||
const cached = _metricsElements.get(target.id);
|
||||
const fpsEl = cached?.fps || document.querySelector(`[data-fps-text="${target.id}"]`);
|
||||
if (fpsEl) fpsEl.innerHTML = `${fpsActual}<span class="dashboard-fps-target">/${fpsTarget}</span>`;
|
||||
|
||||
const errorsEl = document.querySelector(`[data-errors-text="${target.id}"]`);
|
||||
const errorsEl = cached?.errors || document.querySelector(`[data-errors-text="${target.id}"]`);
|
||||
if (errorsEl) errorsEl.textContent = `${errors > 0 ? '⚠️' : '✅'} ${errors}`;
|
||||
|
||||
// Update health dot
|
||||
const isLed = target.target_type === 'led' || target.target_type === 'wled';
|
||||
if (isLed) {
|
||||
const row = document.querySelector(`[data-target-id="${target.id}"]`);
|
||||
const row = cached?.row || document.querySelector(`[data-target-id="${target.id}"]`);
|
||||
if (row) {
|
||||
const dot = row.querySelector('.health-dot');
|
||||
if (dot && state.device_last_checked != null) {
|
||||
@@ -174,19 +188,55 @@ function _updateRunningMetrics(enrichedRunning) {
|
||||
_saveFpsHistory();
|
||||
}
|
||||
|
||||
function _updateProfilesInPlace(profiles) {
|
||||
for (const p of profiles) {
|
||||
const card = document.querySelector(`[data-profile-id="${p.id}"]`);
|
||||
if (!card) continue;
|
||||
const badge = card.querySelector('.dashboard-badge-active, .dashboard-badge-stopped');
|
||||
if (badge) {
|
||||
if (!p.enabled) {
|
||||
badge.className = 'dashboard-badge-stopped';
|
||||
badge.textContent = t('profiles.status.disabled');
|
||||
} else if (p.is_active) {
|
||||
badge.className = 'dashboard-badge-active';
|
||||
badge.textContent = t('profiles.status.active');
|
||||
} else {
|
||||
badge.className = 'dashboard-badge-stopped';
|
||||
badge.textContent = t('profiles.status.inactive');
|
||||
}
|
||||
}
|
||||
const metricVal = card.querySelector('.dashboard-metric-value');
|
||||
if (metricVal) {
|
||||
const cnt = p.target_ids.length;
|
||||
const active = (p.active_target_ids || []).length;
|
||||
metricVal.textContent = p.is_active ? `${active}/${cnt}` : `${cnt}`;
|
||||
}
|
||||
const btn = card.querySelector('.dashboard-target-actions .btn');
|
||||
if (btn) {
|
||||
btn.className = `btn btn-icon ${p.enabled ? 'btn-warning' : 'btn-success'}`;
|
||||
btn.setAttribute('onclick', `dashboardToggleProfile('${p.id}', ${!p.enabled})`);
|
||||
btn.textContent = p.enabled ? '⏸' : '▶';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _renderPollIntervalSelect() {
|
||||
const sec = Math.round(dashboardPollInterval / 1000);
|
||||
return `<span class="dashboard-poll-wrap" onclick="event.stopPropagation()"><input type="range" class="dashboard-poll-slider" min="1" max="10" value="${sec}" oninput="changeDashboardPollInterval(this.value)" title="${t('dashboard.poll_interval')}"><span class="dashboard-poll-value">${sec}s</span></span>`;
|
||||
}
|
||||
|
||||
let _pollDebounce = null;
|
||||
export function changeDashboardPollInterval(value) {
|
||||
const ms = parseInt(value, 10) * 1000;
|
||||
setDashboardPollInterval(ms);
|
||||
startAutoRefresh();
|
||||
stopPerfPolling();
|
||||
startPerfPolling();
|
||||
const label = document.querySelector('.dashboard-poll-value');
|
||||
if (label) label.textContent = `${value}s`;
|
||||
clearTimeout(_pollDebounce);
|
||||
_pollDebounce = setTimeout(() => {
|
||||
const ms = parseInt(value, 10) * 1000;
|
||||
setDashboardPollInterval(ms);
|
||||
startAutoRefresh();
|
||||
stopPerfPolling();
|
||||
startPerfPolling();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function _getCollapsedSections() {
|
||||
@@ -289,11 +339,18 @@ export async function loadDashboard(forceFullRender = false) {
|
||||
const newRunningIds = running.map(t => t.id).sort().join(',');
|
||||
const prevRunningIds = [..._lastRunningIds].sort().join(',');
|
||||
const hasExistingDom = !!container.querySelector('.dashboard-perf-persistent');
|
||||
if (!forceFullRender && hasExistingDom && newRunningIds === prevRunningIds && newRunningIds !== '') {
|
||||
const structureUnchanged = hasExistingDom && newRunningIds === prevRunningIds;
|
||||
if (structureUnchanged && !forceFullRender && running.length > 0) {
|
||||
_updateRunningMetrics(running);
|
||||
set_dashboardLoading(false);
|
||||
return;
|
||||
}
|
||||
if (structureUnchanged && forceFullRender) {
|
||||
if (running.length > 0) _updateRunningMetrics(running);
|
||||
_updateProfilesInPlace(profiles);
|
||||
set_dashboardLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (profiles.length > 0) {
|
||||
const activeProfiles = profiles.filter(p => p.is_active);
|
||||
@@ -475,7 +532,7 @@ function renderDashboardProfile(profile) {
|
||||
const activeCount = (profile.active_target_ids || []).length;
|
||||
const targetsInfo = isActive ? `${activeCount}/${targetCount}` : `${targetCount}`;
|
||||
|
||||
return `<div class="dashboard-target dashboard-profile">
|
||||
return `<div class="dashboard-target dashboard-profile" data-profile-id="${profile.id}">
|
||||
<div class="dashboard-target-info">
|
||||
<span class="dashboard-target-icon">📋</span>
|
||||
<div>
|
||||
|
||||
@@ -77,7 +77,7 @@ function _createChart(canvasId, color, fillColor) {
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: true,
|
||||
animation: false,
|
||||
plugins: { legend: { display: false }, tooltip: { enabled: false } },
|
||||
scales: {
|
||||
x: { display: false },
|
||||
|
||||
Reference in New Issue
Block a user