Aller au contenu
Sylvain Joffraud l4w
Retour

Leaflet : `marker.remove()` ne nettoie pas son `LayerGroup`

Pattern courant dans une app cartographique Leaflet : un mode temporaire (overlay, sélection, recherche) qui ajoute des markers à un L.layerGroup dédié, puis les retire quand on quitte le mode. Activable/désactivable plusieurs fois pendant la session.

const overlayLayer = L.layerGroup();
let userMarker = null;
let userAccuracyCircle = null;
let connectionLine = null;

function enterMode() {
  map.addLayer(overlayLayer);
  userMarker = L.marker([lat, lon]).addTo(overlayLayer);
  // ... idem pour les autres
}

function exitMode() {
  if (userMarker)         { userMarker.remove();         userMarker = null; }
  if (userAccuracyCircle) { userAccuracyCircle.remove(); userAccuracyCircle = null; }
  if (connectionLine)     { connectionLine.remove();     connectionLine = null; }
  map.removeLayer(overlayLayer);
}

Visuellement à l’écran ça marche : les markers disparaissent quand on exitMode(). Aucune erreur console.

Symptôme

Au bout de quelques cycles enter/exit (3-4 fois), on observe :

Le test E2E pour un mode rapide enter/exit/enter révèle le bug : on attend 1 marker au final, on en compte 4.

Cause

marker.remove() (héritée de L.Layer) appelle this._map.removeLayer(this). Ça retire le marker de la carte (du DOM, des panes Leaflet).

Mais ça ne retire pas le marker du LayerGroup qui le contenait.

Le LayerGroup garde une référence interne dans son _layers dict (clé = L.stamp(marker)). Cette référence n’est touchée par aucun marker.remove() ou marker.removeFrom(map). Pour retirer du LayerGroup, il faut explicitement layerGroup.removeLayer(marker).

Conséquence du cycle :

  1. enterMode() → marker créé, ajouté à overlayLayer, qui est ajouté à la map → DOM rendu
  2. exitMode()marker.remove() retire du DOM mais pas du _layers de overlayLayer, puis map.removeLayer(overlayLayer) détache le groupe
  3. enterMode() à nouveau → map.addLayer(overlayLayer) ré-attache le groupe avec ses anciens markers fantômes dans _layers, qui se rajoutent au DOM
  4. Un nouveau marker est créé et ajouté → 2 markers en DOM
  5. Cycles suivants → exponential leak

Le bug est invisible à 1-2 cycles (juste 1 doublon, masqué visuellement par le marker actif). Apparent à partir de 3-4 cycles.

Solution

Utiliser layerGroup.clearLayers() à la sortie du mode. Cette méthode itère le _layers interne et appelle removeLayer pour chaque enfant, ce qui les détache à la fois du groupe et de la map en cascade.

function exitMode() {
  overlayLayer.clearLayers();       // ← unique nettoyage, détache tout
  userMarker = null;
  userAccuracyCircle = null;
  connectionLine = null;
  map.removeLayer(overlayLayer);    // détache le groupe (vide) de la map
}

Plus court ET plus robuste : pas d’oubli quand on ajoute de nouveaux types d’éléments au groupe, tout est nettoyé en une ligne.

Lessons learned

#leaflet #javascript #frontend #mapping #memory-leak #troubleshooting

Questions fréquentes

Pourquoi mes markers Leaflet se dupliquent à chaque add/remove malgré marker.remove() ?
Parce que marker.remove() retire le marker de la carte (du DOM et des panes) mais ne le supprime pas du LayerGroup qui le contenait. Le groupe garde une référence interne dans son dict _layers, et au prochain addLayer du groupe ces markers fantômes reviennent dans le DOM. Le leak grossit à chaque cycle enter/exit.
Quelle est la différence entre marker.remove() et layerGroup.removeLayer(marker) dans Leaflet ?
marker.remove() (hérité de L.Layer) appelle this._map.removeLayer(this) et ne détache le marker que de la carte. Pour le retirer du groupe parent, il faut explicitement layerGroup.removeLayer(marker), qui agit sur le dict _layers du groupe. C’est une asymétrie : on ajoute via le LayerGroup mais on retire via la map, donc le groupe n’est jamais nettoyé.
Comment vider proprement un LayerGroup Leaflet et éviter le memory leak ?
Utilisez layerGroup.clearLayers() à la sortie du mode : la méthode itère le _layers interne et appelle removeLayer sur chaque enfant, ce qui les détache à la fois du groupe et de la carte en cascade. Une seule ligne suffit et rien n’est oublié, même si vous ajoutez de nouveaux types d’éléments au groupe.
Pourquoi le bug de doublons Leaflet n’apparaît qu’au bout de 3 ou 4 cycles ?
Aux premiers cycles les markers s’empilent au même pixel, donc le doublon est masqué visuellement par le marker actif et aucune erreur console n’apparaît. Il devient visible vers le 3e ou 4e enter/exit. Pour diagnostiquer, loggez overlayLayer.getLayers().length à différents moments : si le count monte de cycle en cycle, c’est ce bug.

Partager cet article :

Article précédent
Edge marker Leaflet : caché sous les contrôles, intappable
Article suivant
Wake Lock API : relâché en arrière-plan, à ré-acquérir