From 325fdbe50ebb094d3901fb21aaae8cdcfb7904f2 Mon Sep 17 00:00:00 2001 From: you Date: Tue, 24 Mar 2026 17:53:29 +0000 Subject: [PATCH] fix: heatmap opacity flash on new packet arrival When new data arrived, toggleHeatmap() destroyed and recreated the heat layer, causing a brief flash at full opacity before the CSS opacity was applied via setTimeout. Now reuses the existing layer via setLatLngs() for data updates, and hooks the 'add' event for immediate opacity on first creation. No more flash. All 12 E2E tests pass locally. --- public/index.html | 50 +++++++++++++++++++++++------------------------ public/map.js | 36 +++++++++++++++++++++------------- 2 files changed, 47 insertions(+), 39 deletions(-) diff --git a/public/index.html b/public/index.html index 43ecf3d..895e4d1 100644 --- a/public/index.html +++ b/public/index.html @@ -22,9 +22,9 @@ - - - + + + @@ -81,27 +81,27 @@
- - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/map.js b/public/map.js index a961e45..3eb9d54 100644 --- a/public/map.js +++ b/public/map.js @@ -722,27 +722,35 @@ } function toggleHeatmap(on) { - if (heatLayer) { map.removeLayer(heatLayer); heatLayer = null; window._meshcoreHeatLayer = null; } - if (!on || !map) return; + if (!on || !map) { + if (heatLayer) { map.removeLayer(heatLayer); heatLayer = null; window._meshcoreHeatLayer = null; } + return; + } const points = nodes .filter(n => n.lat != null && n.lon != null) .map(n => { const weight = n.advert_count || 1; return [n.lat, n.lon, weight]; }); - if (points.length && typeof L.heatLayer === 'function') { - var savedOpacity = parseFloat(localStorage.getItem('meshcore-heatmap-opacity')); - if (isNaN(savedOpacity)) savedOpacity = 0.25; - heatLayer = L.heatLayer(points, { - radius: 25, blur: 15, maxZoom: 14, minOpacity: 0.05, - gradient: { 0.2: '#0d47a1', 0.4: '#1565c0', 0.6: '#42a5f5', 0.8: '#ffca28', 1.0: '#ff5722' } - }).addTo(map); - // Set overall layer opacity (affects all gradient colors, not just minimum) - heatLayer.getContainer && heatLayer.getContainer() ? - (heatLayer.getContainer().style.opacity = savedOpacity) : - setTimeout(function() { if (heatLayer._canvas) heatLayer._canvas.style.opacity = savedOpacity; }, 100); - window._meshcoreHeatLayer = heatLayer; + if (!points.length || typeof L.heatLayer !== 'function') return; + var savedOpacity = parseFloat(localStorage.getItem('meshcore-heatmap-opacity')); + if (isNaN(savedOpacity)) savedOpacity = 0.25; + // Update existing layer data without recreating (avoids opacity flash) + if (heatLayer) { + heatLayer.setLatLngs(points); + return; } + heatLayer = L.heatLayer(points, { + radius: 25, blur: 15, maxZoom: 14, minOpacity: 0.05, + gradient: { 0.2: '#0d47a1', 0.4: '#1565c0', 0.6: '#42a5f5', 0.8: '#ffca28', 1.0: '#ff5722' } + }); + // Set opacity on canvas BEFORE it's visible — hook the 'add' event + heatLayer.on('add', function() { + var canvas = heatLayer._canvas || (heatLayer.getContainer && heatLayer.getContainer()); + if (canvas) canvas.style.opacity = savedOpacity; + }); + heatLayer.addTo(map); + window._meshcoreHeatLayer = heatLayer; } let _themeRefreshHandler = null;