diff --git a/frontend-nuxt/assets/css/leaflet-overrides.css b/frontend-nuxt/assets/css/leaflet-overrides.css index 05348f8..1c23fa2 100644 --- a/frontend-nuxt/assets/css/leaflet-overrides.css +++ b/frontend-nuxt/assets/css/leaflet-overrides.css @@ -9,17 +9,15 @@ visibility: visible !important; } -/* Brief highlight when a tile is updated via SSE (tile freshness indicator). */ +/* Subtle highlight when a tile is updated via SSE (reduced intensity to limit flicker). */ @keyframes tile-fresh-glow { 0% { - filter: brightness(1.15); - box-shadow: 0 0 0 0 oklch(0.6 0.2 264 / 0.4); + opacity: 0.92; } 100% { - filter: brightness(1); - box-shadow: none; + opacity: 1; } } .leaflet-tile.tile-fresh { - animation: tile-fresh-glow 0.6s ease-out; + animation: tile-fresh-glow 0.4s ease-out; } diff --git a/frontend-nuxt/components/MapView.vue b/frontend-nuxt/components/MapView.vue index 2323ad0..706522e 100644 --- a/frontend-nuxt/components/MapView.vue +++ b/frontend-nuxt/components/MapView.vue @@ -439,13 +439,21 @@ onMounted(async () => { ;(mapInit.coordLayer.options as { visible?: boolean }).visible = v mapInit.coordLayer.setOpacity(v ? 1 : 0) mapInit.coordLayer.redraw?.() - if (v && leafletMap) { - mapInit.coordLayer.bringToFront?.() - leafletMap.invalidateSize() - } + if (v) mapInit.coordLayer.bringToFront?.() } }) + watch( + () => fullscreen.isFullscreen.value, + () => { + nextTick(() => { + requestAnimationFrame(() => { + if (leafletMap) leafletMap.invalidateSize() + }) + }) + } + ) + watch(mapLogic.state.hideMarkers, (v) => { layersManager?.refreshMarkersVisibility(v) }) @@ -496,9 +504,6 @@ onMounted(async () => { leafletMap.on('moveend', () => mapLogic.updateDisplayCoords(leafletMap)) mapLogic.updateDisplayCoords(leafletMap) - leafletMap.on('zoomend', () => { - if (leafletMap) leafletMap.invalidateSize() - }) leafletMap.on('drag', () => { mapLogic.state.trackingCharacterId.value = -1 }) diff --git a/frontend-nuxt/composables/useMapInit.ts b/frontend-nuxt/composables/useMapInit.ts index 290ec9a..45df8af 100644 --- a/frontend-nuxt/composables/useMapInit.ts +++ b/frontend-nuxt/composables/useMapInit.ts @@ -72,7 +72,7 @@ export async function initLeafletMap( zoomReverse: true, tileSize: TileSize, updateWhenIdle: true, - keepBuffer: 2, + keepBuffer: 4, }) layer.map = initialMapId layer.invalidTile = @@ -88,7 +88,7 @@ export async function initLeafletMap( tileSize: TileSize, opacity: 0.5, updateWhenIdle: true, - keepBuffer: 2, + keepBuffer: 4, }) overlayLayer.map = -1 overlayLayer.invalidTile = diff --git a/frontend-nuxt/composables/useMapUpdates.ts b/frontend-nuxt/composables/useMapUpdates.ts index c16da98..7df1d1b 100644 --- a/frontend-nuxt/composables/useMapUpdates.ts +++ b/frontend-nuxt/composables/useMapUpdates.ts @@ -57,6 +57,21 @@ export function startMapUpdates(options: UseMapUpdatesOptions): UseMapUpdatesRet let batch: TileUpdate[] = [] let batchScheduled = false + const VISIBLE_TILE_BUFFER = 1 + + function getVisibleTileBounds() { + const zoom = map.getZoom() + const px = map.getPixelBounds() + if (!px) return null + return { + zoom, + minX: Math.floor(px.min.x / TileSize) - VISIBLE_TILE_BUFFER, + maxX: Math.ceil(px.max.x / TileSize) + VISIBLE_TILE_BUFFER, + minY: Math.floor(px.min.y / TileSize) - VISIBLE_TILE_BUFFER, + maxY: Math.ceil(px.max.y / TileSize) + VISIBLE_TILE_BUFFER, + } + } + function applyBatch() { batchScheduled = false if (batch.length === 0) return @@ -67,7 +82,14 @@ export function startMapUpdates(options: UseMapUpdatesOptions): UseMapUpdatesRet layer.cache[key] = u.T overlayLayer.cache[key] = u.T } + const visible = getVisibleTileBounds() for (const u of updates) { + if (visible && u.Z !== visible.zoom) continue + if ( + visible && + (u.X < visible.minX || u.X > visible.maxX || u.Y < visible.minY || u.Y > visible.maxY) + ) + continue if (layer.map === u.M) layer.refresh(u.X, u.Y, u.Z) if (overlayLayer.map === u.M) overlayLayer.refresh(u.X, u.Y, u.Z) } diff --git a/frontend-nuxt/lib/SmartTileLayer.ts b/frontend-nuxt/lib/SmartTileLayer.ts index 23caff5..cdaaf15 100644 --- a/frontend-nuxt/lib/SmartTileLayer.ts +++ b/frontend-nuxt/lib/SmartTileLayer.ts @@ -71,12 +71,14 @@ export const SmartTileLayer = L.TileLayer.extend({ const key = `${x}:${y}:${zoom}` const tile = this._tiles[key] - if (tile?.el) { - tile.el.src = this.getTrueTileUrl({ x, y }, z) - tile.el.classList.add('tile-fresh') - const el = tile.el - setTimeout(() => el.classList.remove('tile-fresh'), 600) - } + if (!tile?.el) return + const newUrl = this.getTrueTileUrl({ x, y }, z) + if (tile.el.dataset.tileUrl === newUrl) return + tile.el.dataset.tileUrl = newUrl + tile.el.src = newUrl + tile.el.classList.add('tile-fresh') + const el = tile.el + setTimeout(() => el.classList.remove('tile-fresh'), 400) }, }) as unknown as new (urlTemplate: string, options?: L.TileLayerOptions) => L.TileLayer & { cache: SmartTileLayerCache