Zoom animé : tuiles scalées d'un bloc comme le fond de carte

Les marqueurs Leaflet glissent pendant l'animation de zoom sans changer
de taille : les tuiles LiDAR nageaient sur le fond OSM puis sautaient à
la fin du zoom. Elles vivent désormais dans un pane dédié (lidarTiles)
qui reçoit la même mise à l'échelle que les couches de dalles (translate
+ scale au zoomanim, transition CSS partagée leaflet-zoom-animated) ;
markerZoomAnimation est désactivé pour éviter le double mouvement.
This commit is contained in:
Antoine Jacquin
2026-09-16 21:34:44 +02:00
parent daed457adb
commit 328af123ad

View File

@ -1633,9 +1633,10 @@ input[type="range"]::-moz-range-thumb {
/* Layout et peinture des tuiles hors écran skippés par le navigateur */
content-visibility: auto;
}
/* Uniquement pendant l'animation de zoom (classe posée par app.js) :
scaling sur le GPU, puis libération pour ne pas immobiliser de mémoire. */
body.zooming .ltile { will-change: transform; }
/* Uniquement pendant l'animation de zoom (classe posée par app.js) : le pane
des tuiles porte le scaling — promotion GPU de lui seul (et non des ~1000
div de tuiles), puis libération pour ne pas immobiliser de mémoire. */
body.zooming .leaflet-lidarTiles-pane { will-change: transform; }
.ltile img {
position: absolute; inset: 0;
width: 100%; height: 100%; object-fit: fill; display: block;
@ -1912,10 +1913,23 @@ const map = L.map('viewport', {
zoomControl: false,
attributionControl: false,
minZoom: 4,
maxZoom: 19
maxZoom: 19,
// Les marqueurs ne doivent pas glisser individuellement pendant le zoom :
// le pane lidarTiles est mis à l'échelle d'un bloc (voir zoomanim ci-dessous),
// le glissement propre des icônes s'ajouterait au scaling (double mouvement).
// Seul effet collatéral : le marqueur GPS saute à la fin d'un zoom.
markerZoomAnimation: false
});
// Pane dédié au dessin de zones : les tuiles LiDAR sont des marqueurs
// (pane 600) et les vecteurs par défaut vivent en dessous (pane 400) ;
// Pane dédié aux tuiles LiDAR (marqueurs) : pendant l'animation de zoom il
// reçoit la même mise à l'échelle que le fond de carte (translate + scale,
// transition CSS partagée via la classe leaflet-zoom-animated) — voir le
// gestionnaire zoomanim plus bas. La classe .leaflet-zoom-animated fournit
// aussi transform-origin: 0 0 (feuille de style Leaflet).
const lidarTilesPane = map.createPane('lidarTiles');
lidarTilesPane.style.zIndex = 600;
lidarTilesPane.classList.add('leaflet-zoom-animated');
// Pane dédié au dessin de zones : les tuiles LiDAR vivent en dessous (pane
// lidarTiles, 600) et les vecteurs par défaut vivent en dessous (pane 400) ;
// on place rectangle et cellules fantômes au-dessus (620), sans capter
// les clics (pointer-events: none → la carte reste manipulable).
map.createPane('genDraw');
@ -2022,7 +2036,8 @@ function registerTile(t) {
const mk = L.marker(L.latLng(center[0], center[1]), {
icon: icon,
interactive: true,
keyboard: false
keyboard: false,
pane: 'lidarTiles'
}).addTo(map);
mk.on('click', () => {
if (exportMode) { toggleExportSel(t); return; }
@ -2736,8 +2751,24 @@ gpsBtn.addEventListener('click', () => {
}
});
// will-change pendant l'animation de zoom uniquement : le scaling passe sur
// le GPU, et les couches sont libérées juste après (mémoire vidéo).
// le GPU, et la couche est libérée juste après (mémoire vidéo).
let zoomGpuTimer = null;
// Animation de zoom des tuiles calée sur le fond de carte : les marqueurs
// Leaflet glissent (transition de position) sans changer de taille — les
// tuiles LiDAR « nageaient » donc sur le fond pendant l'animation puis
// sautaient à la fin. On applique au pane entier la mise à l'échelle que
// Leaflet applique aux couches de dalles : translate + scale autour de
// l'origine de la vue cible. La cible coïncide exactement avec la position
// que Marker._animateZoom donnerait à chaque icône (project(latlng, z1) −
// _getNewPixelOrigin(center, z1)) : le swap à la fin du zoom est sans saut.
map.on('zoomanim', (e) => {
const s = map.getZoomScale(e.zoom, map.getZoom());
const po0 = map.getPixelOrigin();
const po1 = map._getNewPixelOrigin(e.center, e.zoom);
lidarTilesPane.style.transform =
'translate3d(' + (s * po0.x - po1.x) + 'px,' + (s * po0.y - po1.y) + 'px,0) scale(' + s + ')';
});
map.on('zoomend', () => { lidarTilesPane.style.transform = ''; });
map.on('zoomstart', () => {
clearTimeout(zoomGpuTimer);
document.body.classList.add('zooming');