11534 sujets

JavaScript, DOM et API Web HTML5

Bonjour à tous,

J'ai un comportement bizarre en utilisant Leaflet dans un contexte WordPress dans l'éditeur Gutenberg.
J'ai rajouté des meta données sur une taxonomie et parmi lesquelles un jeu de coordonnées GPs (lat, lng) et j'affiche une carte Leaflet à partir de ces coordonnées.

Lorsque j'affiche la carte dans l'éditeur Gutenberg, en bas dans la zone des meta données, la carte ne s'affiche pas correctement, le supposé centre (avec le marker s'affiche dans le coin supérieur gauche (voir image) et toutes les 'tiles' ne sont pas chargées (partie haute de l'image).
Par un pur hasard, je me suis aperçu que si je redimensionnais la fenêtre du navigateur alors la carte s'affiche bien, avec le marker au centre (partie basse de l'image).

J'ai résolu ce problème, un peu à la sauvage, en provoquant un rafraichissement légèrement retardé, comme ceci:


setTimeout(gibiResize, 300); 

function gibiResize() {
	window.dispatchEvent(new Event('resize'))
}


et ainsi la carte s'affiche bien, mais je ne comprends toujours pas pourquoi j'ai ce problème...

Quelqu'un a t-il une idée?

-
upload/1738078775-40948-probleme-leaflet.jpg
Modifié par lionel_css3 (28 Jan 2025 - 16:50)
Pour le fonctionnement, j'envoie les données latitude et longitude au script JS à partir de PHP


				// on envoie les données au fichier javascript pour initialisation
				wp_localize_script(
					'events-manage-location-js', 
					'object_map', 
					array(
						'lat' => esc_html($location_data[0]),
						'lng' => esc_html($location_data[1])
					)
				);


et je les récupère dans Javascript

	// =======initialisation carte si lieu est défini au chargement
	if (typeof(object_map)!="undefined") {
		let lat = object_map.lat;
		let lng = object_map.lng;

		map.setView([lat, lng], 16);
		marker = L.marker([lat, lng]);
		marker.addTo(map);

		setTimeout(gibiResize, 300);  // pour le bug de refresh
	}



Une fois que la page a été chargée, si je charge une autre carte avec la liste <select>, je n'ai pas le problème...

	// =======chargement nouvelle carte sur <select>
	//   création ou mise à jour event
	$("#evt-location-selector").on('change', function(e) {
		let $itemSelected = $("#evt-location-selector option:selected");
		let newName = $itemSelected.data('name');
		let newLat = $itemSelected.data('lat');
		let newLng = $itemSelected.data('lng');
		let newAdr = $itemSelected.data('adr');
		//console.log(`${newName} ${newLat} ${newLng} ${newAdr}`);

		map.setView([newLat, newLng], 16);
		marker.remove();
		marker = L.marker([newLat, newLng]).addTo(map);

		$("#location-address").text(newAdr);
	});


-
Bonsoir,

Ça ressemble à un problème de chargement : la map doit se charger avant d'autres éléments dans la page et le rendu final s'en trouve ainsi cassé. Personnellement je fais ainsi pour charger mes scripts Leaflet (la fonction ici s'appelle "maps") :
/**
 * Test la présence de la bibliothèque Leaflet.js chargée en asynchrone et, si oui, exécution du script de configuration.
 * @param {object} L est une variable globale produite par Leaflet, elle nous permettra de tester l'initialisation de la bibliothèque.
 * @note typeof permet de tester la variable sans que celle-ci produise une erreur si elle n'est pas définie.
 */
window.addEventListener('load', () => {
  if (typeof L !== 'undefined') maps()

  document.addEventListener('readystatechange', () => {
    if (document.readyState === 'complete' && typeof L !== 'undefined') maps()
  })
})
Voici une version un peu plus claire et moins redondante :
/**
 * Vérifie la présence de la bibliothèque Leaflet.js chargée en asynchrone et exécute le script de configuration si disponible.
 * @param {object} L - Variable globale définie par Leaflet, utilisée pour vérifier l'initialisation de la bibliothèque.
 */
window.addEventListener('load', () => {
  const initializeMaps = () => {
    if (typeof L !== 'undefined') {
      maps()
    }
  }

  // Exécuter lorsque le chargement de la page est terminé
  initializeMaps()

  // Ajouter un listener pour les changements de readyState
  document.addEventListener('readystatechange', () => {
    if (document.readyState === 'complete') {
      initializeMaps()
    }
  })
})
Votre approche avec setTimeout(gibiResize, 300); fonctionne, mais ce n'est pas une solution élégante. Le problème vient du fait que Leaflet a besoin d'un conteneur correctement chargé et visible pour afficher correctement la carte. Lorsque l'éditeur Gutenberg charge et rend les composants de manière asynchrone, la carte peut être initialisée au mauvais moment, ce qui empêche le rendu correct des tuiles.

Voici quelques solutions à tester :

1. Utiliser invalidateSize() après l'initialisation de la carte
Leaflet propose la méthode invalidateSize() qui force le recalcul de la taille de la carte après son chargement. Ajoutez ceci après avoir défini la vue de la carte :

if (typeof object_map !== "undefined") {
let lat = object_map.lat;
let lng = object_map.lng;

map.setView([lat, lng], 16);
marker = L.marker([lat, lng]);
marker.addTo(map);

setTimeout(() => {
map.invalidateSize();
}, 300);
}
2. Écouter les événements DOMContentLoaded ou resize
Plutôt que d'introduire un délai artificiel, ajoutez un écouteur d'événements pour DOMContentLoaded, load ou resize :

window.addEventListener('load', () => {
if (typeof object_map !== "undefined") {
let { lat, lng } = object_map;
map.setView([lat, lng], 16).whenReady(() => map.invalidateSize());
L.marker([lat, lng]).addTo(map);
}
});
Avec .whenReady(), invalidateSize() s'exécutera uniquement lorsque la carte sera complètement chargée, éliminant ainsi le besoin de setTimeout().
Modifié par robertkaczor (29 Jan 2025 - 13:47)
Olivier C a écrit :
Bonsoir,

Ça ressemble à un problème de chargement : la map doit se charger avant d'autres éléments dans la page et le rendu final s'en trouve ainsi cassé. Personnellement je fais ainsi pour charger mes scripts Leaflet (la fonction ici s'appelle "maps") :


Merci Olivier, je vais étudier ton code...
Quand tu dis "la map doit se charger avant d'autres élément" tu veux dire qu'elle se charge effectivement avant les autres éléments alors qu'elle ne devrait pas, ou bien tu veux dire qu'elle doit effectivement se charger avant pour que tout fonctionne bien? Smiley ravi
de surcroit j'utilise (par habitude avec Wordpress le jQuery ready() au lieu de addEventListener('load') mais je pense que ça doit marcher pareil?
Modifié par lionel_css3 (29 Jan 2025 - 15:12)
robertkaczor a écrit :
Votre approche avec setTimeout(gibiResize, 300); fonctionne, mais ce n'est pas une solution élégante. Le problème vient du fait que Leaflet a besoin d'un conteneur correctement chargé et visible pour afficher correctement la carte. Lorsque l'éditeur Gutenberg charge et rend les composants de manière asynchrone, la carte peut être initialisée au mauvais moment, ce qui empêche le rendu correct des tuiles.

Voici quelques solutions à tester :

1. Utiliser invalidateSize() après l'initialisation de la carte
Leaflet propose la méthode invalidateSize() qui force le recalcul de la taille de la carte après son chargement. Ajoutez ceci après avoir défini la vue de la carte :


2. Écouter les événements DOMContentLoaded ou resize
Plutôt que d'introduire un délai artificiel, ajoutez un écouteur d'événements pour DOMContentLoaded, load ou resize :

Avec .whenReady(), invalidateSize() s'exécutera uniquement lorsque la carte sera complètement chargée, éliminant ainsi le besoin de setTimeout().


merci robert,

effectivement oui, ma solution n'est pas la plus élégante et je le reconnais Smiley confus

je ne connaissais pas la fonction invalidateSize() je vais étudier ça..
Bonsoir Lionel,

Il y a deux points à considérer :

- la bibliothèque peut se charger après le script de configuration, ce qui est déjà un problème.
- d'autres scripts qui ne sont pas directement liés à Leaflet peuvent se charger après et casser le rendu de la carte qui était pourtant correctement finalisé.

Donc, pour ces deux raisons, il faut vérifier :
- que la page soit entièrement chargée, (donc vérifier l'événement 'load', soit en javascript natif, soit via jQuery).
- que la bibliothèque Leaflet soit bien chargée avant de charger le fichier de configuration pour cette carte (avec ma solution le contrôle de la variable globale "L") ; situation nécessaire si les chargements se font en asynchrone.
Ma solution plus haut tiens compte de ces deux problèmes.

Pour le plaisir, voici encore une amélioration du script, toujours en JS vanilla, mais cette fois avec un MutationObserver en lieu et place d'une surveillance de l'événement 'readystatechange' et même de 'load' :
/**
 * Vérifie si la bibliothèque Leaflet.js est chargée et exécute le script de configuration.
 */
function initializeMaps() {
  if (window.L) {
    maps()
    return true // Indique que l'initialisation a eu lieu
  }
  return false
}

// Exécuter immédiatement si Leaflet est déjà chargé
if (!initializeMaps()) {
  // Surveiller les mutations du DOM pour détecter l'ajout de Leaflet
  const observer = new MutationObserver(() => {
    if (initializeMaps()) {
      observer.disconnect() // Arrêter l'observation une fois Leaflet détecté
    }
  })

  observer.observe(document.documentElement, { childList: true, subtree: true })
}

On remarquera l'emploi de window.L en lieu et place d'une vérification avec typeof.
Modifié par Olivier C (29 Jan 2025 - 23:04)
Olivier C a écrit :


/**
 * Vérifie si la bibliothèque Leaflet.js est chargée et exécute le script de configuration.
 */
function initializeMaps() {
  if (window.L) {
    maps()
    return true // Indique que l'initialisation a eu lieu
  }
  return false
}




d'une manière générale, qu'appelles tu maps() ?
C'est ton code pour générer la carte Leaftlet?
Oui, maps() représente la fonction contenant le code de configuration.
Modifié par Olivier C (30 Jan 2025 - 10:53)
Alors, Olivier C j'ai pas réussi à faire marcher ta solution..

avec ce code

if (typeof L !== 'undefined') {
      maps()
    }

si L n'est pas définit, on a pas de seconde chance quand il est défini, pour ce que j'ai compris.

par contre la solution de robertkaczor fonctionne mais pour moi elle équivaut à ce que je faisais avec window.dispatchEvent(new Event('resize'))...
Je vais quand même adopter cette solution puisque invalidateSize est une fonction de Leaflet, ça fera moins 'hack'

voici mon code actuel..



let map;
let marker;	

jQuery(document).ready(function($)  {
	// création de la carte commune



	map = L.map('location-map-preview');
	L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
		maxZoom: 19,
		attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
	}).addTo(map);


	// =======initialisation carte si lieu est défini au chargement
	if (typeof(object_map)!="undefined") {
		let lat = object_map.lat;
		let lng = object_map.lng;

		map.setView([lat, lng], 16);
		marker = L.marker([lat, lng]);
		marker.addTo(map);

		//setTimeout(gibiResize, 300);  // pour le bug de refresh

		setTimeout(() => {
			map.invalidateSize();
			}, 300);
	}
	
    // suite du script
    // Opérations sur la carte

});

Désolé pour le manque de réactivité. Juste :
a écrit :
si L n'est pas définit, on a pas de seconde chance quand il est défini, pour ce que j'ai compris.

Si si, c'est le MutationObserver qui se charge de vérifier la disponibilité de la lib'. Chez moi je fais ceci (petite optimisation : l'observance du head uniquement et pas de tout le document, pour des raisons de performances) :
function initializeMaps(callback) {
  if (window.L) {
    callback()
    return true
  }
  return false
}

if (!initializeMaps(maps)) {
  const observer = new MutationObserver(() => {
    if (initializeMaps(maps)) {
      observer.disconnect()
    }
  })

  // Observer uniquement le head
  observer.observe(document.head, { childList: true, subtree: true })

  // Arrêter l'observation après 10 secondes
  setTimeout(() => observer.disconnect(), 10000)
}

Modifié par Olivier C (08 Feb 2025 - 22:29)