perf(beacons): coverage map uses Leaflet canvas renderer, no sticky tooltips
The 'show estimated current coverage' toggle pushes 1-8k grid cells
to the browser at 5.76 GHz+ and previously rendered each as its own
SVG <path> via Leaflet's default vector renderer. That meant 1-8k
DOM nodes plus per-element layout/paint on every pan/zoom — enough
to lock up tabs on mid-range hardware.
Two changes:
1. All coverage rects share a single L.canvas({padding: 0.2})
renderer. Browser sees one <canvas> element drawn in one pass
per redraw instead of thousands of <path> nodes. Hit-testing for
tooltips still works through Leaflet's canvas-renderer mouse
events.
2. Drop `sticky: true` from the tooltip binding. Sticky tooltips
reposition to follow the cursor on every mousemove, which scales
O(N) with cell count — across thousands of rects that becomes the
dominant cost as the cursor moves. Without sticky, the tooltip
anchors to the cell's centre on hover (same info, cheaper).
This commit is contained in:
parent
b4d6b0417b
commit
90195d082e
1 changed files with 19 additions and 2 deletions
|
|
@ -61,6 +61,23 @@ export const BeaconMap = {
|
|||
// event, which the server only sends when the user flips the
|
||||
// toggle on. Keeping this off the mount path avoids parsing tens
|
||||
// of thousands of cells into Leaflet rects on every page load.
|
||||
//
|
||||
// Coverage rects all share a single L.canvas() renderer instead
|
||||
// of Leaflet's default SVG renderer. With SVG, each cell becomes
|
||||
// its own <path> element — at 5.76 GHz+ a typical estimate is
|
||||
// ~3-8k cells, which means ~3-8k DOM nodes plus per-element layout
|
||||
// / paint work on every pan/zoom. Canvas collapses that to a
|
||||
// single <canvas> element, drawn in one pass per redraw.
|
||||
//
|
||||
// Hit-testing for tooltips still works through Leaflet's canvas
|
||||
// renderer (it walks the layer tree at mousemove time), so the
|
||||
// per-cell tooltip is preserved. We do drop `sticky: true`
|
||||
// though: that flag re-projects the tooltip to follow the cursor
|
||||
// pixel-by-pixel, which against thousands of rects compounds
|
||||
// every mousemove with N geometry checks. Without sticky, the
|
||||
// tooltip anchors to the cell's centre — same information,
|
||||
// multiple-orders-of-magnitude cheaper.
|
||||
const coverageRenderer = L.canvas({padding: 0.2})
|
||||
let cellLayer: L.LayerGroup | null = null
|
||||
let cellBounds: [number, number][] = []
|
||||
|
||||
|
|
@ -77,6 +94,7 @@ export const BeaconMap = {
|
|||
const sw: [number, number] = [c.lat - half, c.lon - half]
|
||||
const ne: [number, number] = [c.lat + half, c.lon + half]
|
||||
const rect = L.rectangle([sw, ne], {
|
||||
renderer: coverageRenderer,
|
||||
color: c.color,
|
||||
weight: 0,
|
||||
fillColor: c.color,
|
||||
|
|
@ -84,8 +102,7 @@ export const BeaconMap = {
|
|||
interactive: true
|
||||
})
|
||||
rect.bindTooltip(
|
||||
`${c.label}<br>${c.rx_dbm} dBm<br>${formatDistanceKm(c.distance_km)} · score ${c.score}`,
|
||||
{sticky: true}
|
||||
`${c.label}<br>${c.rx_dbm} dBm<br>${formatDistanceKm(c.distance_km)} · score ${c.score}`
|
||||
)
|
||||
rect.addTo(cellLayer)
|
||||
cellBounds.push(sw, ne)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue