Marqueurs SVG Leaflet : aligne la pointe avec iconAnchor
Positionne des marqueurs SVG dans Leaflet en reliant la pointe visible à iconAnchor. Tiens compte des marges du viewBox, de l’ajustement des proportions et de la taille de l’icône.
Sur cette page
Pour un marqueur SVG personnalisé dans Leaflet, définis iconSize aux dimensions de l’image affichée et iconAnchor à la position en pixels de la pointe visible dans cette image. Mesure depuis le coin supérieur gauche de l’image. Les marges transparentes du SVG comptent dans ces dimensions : la moitié de la largeur et la hauteur entière peuvent donc manquer la pointe d’une épingle asymétrique ou entourée de marges.
Ce guide utilise une épingle originale dont la pointe est à droite du centre du corps. Nous comparons deux cadres sources à deux tailles d’icône dans une carte de navigateur sans tuiles. Si tu possèdes déjà un dessin SVG propre, utilise-le directement ; positionner l’ancrage ne nécessite pas de vectorisation.
Sépare le dessin, le cadre de l’image et le point de carte
Trois systèmes de coordonnées se rencontrent ici. Le chemin SVG décrit le dessin en unités sources. iconSize établit son cadre d’image affiché en pixels. Leaflet place le point de ce cadre indiqué par iconAnchor à la position géographique du marqueur. La référence d’Icon définit ces options et l’origine de l’ancrage en haut à gauche.
Une épingle classique peut atteindre le milieu du bord inférieur, mais ton dessin peut finir ailleurs. Le comportement par défaut documenté de Leaflet est un ancrage centré lorsqu’une taille est définie. Les valeurs du bas au centre de notre expérience sont une hypothèse explicite que nous testons, pas cette valeur par défaut.
Le tutoriel officiel des icônes personnalisées montre comment fournir une instance de L.icon à un marqueur. Avec un dessin importé, le travail supplémentaire consiste à trouver le point dans son image qui doit toucher la coordonnée de carte.

Convertis la pointe source en pixels d’icône
La pointe de notre épingle est à la coordonnée source (40, 80). Son cadre serré est viewBox="0 0 60 80". La version avec marges ne change que ce cadre en viewBox="-20 -10 100 120" ; les deux fichiers conservent le même chemin et la même ouverture transparente.
La référence de viewBox décrit ce rectangle source. L’espace vide qu’il contient accompagne le dessin. Notre guide de la boîte englobante SVG explique pourquoi un canevas et une forme peinte peuvent avoir des limites différentes.
Les deux versions utilisent explicitement preserveAspectRatio="xMidYMid meet". Ce réglage ajuste uniformément le cadre dans le viewport et centre l’espace restant. Consulte la référence de preserveAspectRatio de MDN pour ces réglages.
Pour ce mode d’ajustement, appelons W et H les dimensions de l’icône, (minX, minY, vbW, vbH) le viewBox et (tipX, tipY) la pointe source :
s = min(W / vbW, H / vbH)
offsetX = (W - vbW * s) / 2
offsetY = (H - vbH * s) / 2
anchorX = (tipX - minX) * s + offsetX
anchorY = (tipY - minY) * s + offsetYPour l’icône de 60×80 avec marges, s vaut 0.6. Le cadre source de 100×120 occupe 60×72 pixels et laisse quatre pixels au-dessus et au-dessous. La pointe devient [36, 58], en incluant ce décalage vertical. Prendre simplement une fraction de chaque dimension séparément ferait manquer l’étape d’ajustement uniforme.
| Cadre source | Taille de l’icône | Ancrage de pointe calculé |
|---|---|---|
| Serré, 0 0 60 80 | 60×80 | [40, 80] |
| Serré, 0 0 60 80 | 90×120 | [60, 120] |
| Avec marges, -20 -10 100 120 | 60×80 | [36, 58] |
| Avec marges, -20 -10 100 120 | 90×120 | [54, 87] |
Ces calculs s’appliquent à notre image sans transformation et au réglage meet explicitement centré. Un autre alignement, slice ou none exige le mappage de coordonnées correspondant. Notre guide des proportions couvre ces choix. Des transformations CSS supplémentaires sur l’image du marqueur nécessitent aussi une inspection distincte.
Vérifie la position dans une carte sans tuiles
Nous avons utilisé Leaflet 1.9.4, la version stable indiquée sur la page officielle de téléchargement. L’expérience charge localement son JavaScript et son CSS de version fixe, sans ajouter de couche de tuiles. Chaque carte projette la coordonnée [0, 0] sur un réticule bleu ; elle affiche une image SVG à côté de ce point de référence.
L’inspection source a trouvé un chemin composé identique et aucune image matricielle intégrée dans chaque SVG. Les dimensions d’image du navigateur étaient de 60×80 et 90×120 pixels, comme demandé. Avec le rectangle observé de chaque image, nous avons comparé la position convertie de la pointe au point géographique projeté :
| Source | Taille de l’icône | Erreur avec l’ancrage supposé au bas au centre | Erreur avec l’ancrage de pointe calculé |
|---|---|---|---|
| Serrée | 60×80 | 10 px à droite | 0 px |
| Serrée | 90×120 | 15 px à droite | 0 px |
| Avec marges | 60×80 | 6 px à droite, 22 px en haut | 0 px |
| Avec marges | 90×120 | 9 px à droite, 33 px en haut | 0 px |
Le réticule rejoignait aussi visiblement les pointes corrigées. Ce sont des observations de ce fichier de test créé par nos soins dans un navigateur Chrome, pas une garantie pour tous les SVG, navigateurs ou intégrations de carte. L’épingle avec marges reste plus petite dans le même cadre d’image après correction de son ancrage. La position et la taille du dessin sont des décisions distinctes.
Copie une base de marqueur fonctionnelle
Enregistre ce HTML complet sous marker.html et sers-le depuis un serveur web local. Il charge les fichiers Leaflet de version fixe depuis le CDN lié par la page de téléchargement de Leaflet. Le SVG est encodé en URL d’image : l’exemple n’a donc besoin ni de fichier d’icône ni de service de tuiles. Une connexion Internet est nécessaire pour ces deux fichiers de bibliothèque.
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Leaflet SVG tip anchor</title>
<link rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<style>
#map { width: 360px; height: 300px; background: #edf3ef; }
.cross { position: absolute; z-index: 1000; pointer-events: none;
width: 24px; height: 24px; transform: translate(-50%, -50%); }
.cross::before, .cross::after { content: ""; position: absolute;
background: #006cbe; }
.cross::before { top: 12px; width: 24px; height: 1px; }
.cross::after { left: 12px; width: 1px; height: 24px; }
</style>
<div id="map"></div>
<pre id="status">Loading marker…</pre>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="60" height="80" viewBox="-20 -10 100 120" preserveAspectRatio="xMidYMid meet"><path fill="#bf593c" fill-rule="evenodd" d="M30 0 C46 0 60 12 60 28 C60 46 46 54 40 80 C30 62 0 46 0 28 C0 12 14 0 30 0 Z M40 26 A10 10 0 1 0 20 26 A10 10 0 1 0 40 26 Z"/></svg>`;
const size = [60, 80];
const viewBox = [-20, -10, 100, 120];
const sourceTip = [40, 80];
const scale = Math.min(size[0] / viewBox[2], size[1] / viewBox[3]);
const anchor = [
(sourceTip[0] - viewBox[0]) * scale
+ (size[0] - viewBox[2] * scale) / 2,
(sourceTip[1] - viewBox[1]) * scale
+ (size[1] - viewBox[3] * scale) / 2
];
const map = L.map("map", {
zoomControl: false, attributionControl: false,
dragging: false, scrollWheelZoom: false, doubleClickZoom: false,
boxZoom: false, keyboard: false, touchZoom: false
}).setView([0, 0], 2);
const position = [0, 0];
const icon = L.icon({
iconUrl: "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg),
iconSize: size,
iconAnchor: anchor
});
const marker = L.marker(position, { icon }).addTo(map);
const container = document.querySelector("#map");
const cross = document.createElement("span");
cross.className = "cross";
container.append(cross);
const point = map.latLngToContainerPoint(position);
cross.style.left = point.x + "px";
cross.style.top = point.y + "px";
(async () => {
const image = marker.getElement();
await image.decode();
const ir = image.getBoundingClientRect();
const mr = container.getBoundingClientRect();
const displacement = [
ir.left + anchor[0] - mr.left - point.x,
ir.top + anchor[1] - mr.top - point.y
];
document.querySelector("#status").textContent = JSON.stringify({
version: L.version, size, anchor,
observedImageSize: [ir.width, ir.height], displacement
}, null, 2);
})();
</script>
</html>L’état affiché devrait indiquer anchor: [36, 58], une taille d’image de [60, 80] et un déplacement nul. Change size en [90, 120] : le même calcul donne [54, 87]. Pour utiliser le cadre serré, change à la fois le viewBox du SVG et le tableau JavaScript viewBox en 0 0 60 80. Garde-les cohérents.
Le chemin inclut un contour intérieur circulaire avec fill-rule="evenodd", créant une vraie ouverture. La référence de fill-rule de MDN explique la règle. L’exemple n’ajoute ni bordure ni transformation au conteneur de carte ; cela demanderait de la prudence pour comparer les rectangles du viewport aux coordonnées du conteneur.
Examine la destination avant de changer le dessin
Si ton marqueur manque encore sa coordonnée, vérifie dans cet ordre :
- Confirme que l’image SVG est chargée et que l’élément marqueur a la largeur et la hauteur en pixels prévues.
- Localise la pointe source réelle, y compris les transformations appliquées dans le SVG.
- Vérifie le viewBox complet, ses coordonnées minimales et son mode d’ajustement.
- Recalcule l’ancrage à chaque changement du cadre ou de la taille d’icône.
- Retire les remplacements CSS sans rapport pendant le test, puis vérifie à nouveau le style final et les tailles d’affichage prévues.
Garde la coordonnée géographique fixe pendant cette vérification. Déplacer cette coordonnée pour compenser les marges d’image masquerait l’erreur de position du dessin.
Il s’agit d’un processus de marqueur dans le navigateur. Un marqueur SVG QGIS utilise ses propres réglages de destination et sa syntaxe de peinture paramétrée ; conserve un maître SVG ordinaire pour une livraison générale dans les navigateurs.
Si ton icône n’existe qu’en PNG ou JPG et que tu as besoin de contours modifiables, convertis le dessin avec PerfectVector, examine l’ouverture et la pointe, puis enregistre un maître SVG propre. Calcule ensuite son ancrage Leaflet à partir du cadre source final. La vectorisation prépare le dessin ; ton code de carte fournit les lieux et le positionnement. Si le seul problème est un décalage dans un SVG existant, corrige directement son cadre ou son ancrage. Le guide de vectorisation d’images explique quand convertir une source matricielle est utile.
Questions fréquentes
iconAnchor est-il une latitude et une longitude ? Non. C’est une position en pixels dans l’image d’icône affichée, mesurée depuis son coin supérieur gauche. La position géographique du marqueur est fournie séparément.
Pourquoi mon SVG avec marges reste-t-il petit après correction de son ancrage ? Le cadre source vide occupe toujours une partie du cadre d’image. Corriger l’ancrage positionne la pointe ; cela ne supprime pas cet espace vide et n’agrandit pas la forme peinte.
Faut-il toujours utiliser la moitié de la largeur et la hauteur entière ? Utilise cette paire uniquement lorsque le point d’attache visible se trouve réellement là. Notre épingle asymétrique et notre source avec marges nécessitent des valeurs différentes, même si les deux sont des dessins de marqueur de localisation.
Convertir un PNG en SVG corrige-t-il la position du marqueur ? La conversion peut fournir un dessin modifiable. Le cadre source final, les dimensions affichées, l’ancrage et les coordonnées de carte déterminent toujours la position du marqueur.
Sources
- Leaflet — Référence d’Icon — Dimensions en pixels, origine de l’ancrage, centrage par défaut et alignement géographique.
- Leaflet — Marqueurs avec icônes personnalisées — Création d’une icône et affectation à un marqueur.
- Leaflet — Téléchargement — Version stable et liens JavaScript et CSS de version fixe.
- MDN — viewBox — Rectangle de coordonnées source utilisé dans les deux cadres.
- MDN — preserveAspectRatio — Ajustement uniforme et espace restant centré pour xMidYMid meet.
- MDN — fill-rule — Règle pair-impair utilisée pour l’ouverture transparente.
Plus depuis le blog

Images SVG dans Cytoscape.js : ajustement et marges
Ajuste un dessin SVG dans les nœuds Cytoscape.js avec un exemple fonctionnel. Compare les espaces vides de la source, contain et cover, puis vérifie le chargement et les libellés.

Mesurer un tracé SVG sans confondre les échelles
Mesure les tracés SVG en unités utilisateur, tiens compte des transformations et de la taille nominale, et distingue les contours de la calibration pathLength ou du trajet machine.
