> [Site index](https://perfectvector.com/llms.txt) — available pages and descriptions.

# Маркеры SVG в Leaflet: совместите кончик с iconAnchor

> Размещайте собственные SVG-маркеры в Leaflet, сопоставляя видимый кончик с iconAnchor. Учитывайте поля viewBox, вписывание с сохранением пропорций и изменение размера значка.

Source: https://perfectvector.com/ru/blog/leaflet-svg-marker-anchor

Language: ru
Author: Irene Kim
Published: 2026-10-05T08:00:00+09:00
Updated: 2026-10-05T08:00:00+09:00

Для собственного SVG-маркера Leaflet задайте `iconSize` равным размерам показываемого изображения, а `iconAnchor` — пиксельной позиции видимого кончика внутри него. Измеряйте от верхнего левого угла изображения. Прозрачные поля SVG входят в эти размеры, поэтому половина ширины и полная высота могут не попасть в кончик асимметричной метки или метки с полями.

В руководстве используется оригинальная метка, чей кончик находится справа от центра её тела. Мы сравниваем две исходные рамки в двух размерах значка на браузерной карте без тайлов. Если чистая SVG-графика уже есть, используйте её напрямую: размещению якоря векторизация не нужна.

## Отделите графику, прямоугольник изображения и точку карты

Здесь встречаются три системы координат. Контур SVG описывает графику в исходных единицах. `iconSize` задаёт её показываемый прямоугольник в пикселях. Leaflet совмещает точку внутри этого прямоугольника, заданную `iconAnchor`, с географическим положением маркера. [Справочник Icon](https://leafletjs.com/reference.html#icon) определяет эти параметры и начало отсчёта якоря в верхнем левом углу.

Обычная метка может доходить до середины нижнего края, но ваша графика может заканчиваться в другом месте. Описанное в документации значение Leaflet по умолчанию — центральный якорь, если размер указан. Значения нижнего центра в нашем эксперименте — явно проверяемое допущение, а не это значение по умолчанию.

[Официальный урок по собственным значкам](https://leafletjs.com/examples/custom-icons/) показывает передачу экземпляра `L.icon` маркеру. Для импортированной графики дополнительно нужно найти точку внутри изображения, которая должна касаться координаты карты.

**Видимый кончик и якорь**

![Концептуальное сравнение асимметричной метки местоположения на холсте с полями: кончик сначала смещён от перекрестия, затем совпадает с ним](https://perfectvector.com/blog/leaflet-svg-marker-anchor/source-tip-anchor.webp)

*Иллюстрация: учитывайте всю рамку изображения при размещении видимого кончика. Измеренный браузерный эксперимент ниже использует отдельно созданные файлы SVG.*

## Переведите исходный кончик в пиксели значка

Кончик нашей метки находится в исходной координате `(40, 80)`. Плотная рамка — `viewBox="0 0 60 80"`. Вариант с полями меняет только рамку на `viewBox="-20 -10 100 120"`; оба файла сохраняют одинаковый контур и прозрачное отверстие.

[Справочник `viewBox`](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/viewBox) описывает этот исходный прямоугольник. Пустое пространство внутри него перемещается вместе с графикой. Наше [руководство по ограничивающему прямоугольнику SVG](https://perfectvector.com/ru/blog/svg-bounding-box) объясняет, почему у холста и закрашенной фигуры могут быть разные границы.

Обе версии явно используют `preserveAspectRatio="xMidYMid meet"`. Он равномерно вписывает рамку в область просмотра и центрирует оставшееся пространство. Эти настройки описаны в [справочнике MDN по preserveAspectRatio](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/preserveAspectRatio).

Для этого режима вписывания пусть `W` и `H` — размеры значка, `(minX, minY, vbW, vbH)` — viewBox, а `(tipX, tipY)` — исходный кончик:

```text
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 + offsetY
```

Для значка с полями 60×80 `s` равно `0.6`. Исходная рамка 100×120 занимает 60×72 пикселя, оставляя по четыре пикселя сверху и снизу. Кончик становится `[36, 58]` с учётом этого вертикального смещения. Простое вычисление доли каждого размера по отдельности пропустит этап равномерного вписывания.

| Исходная рамка            | Размер значка | Рассчитанный якорь кончика |
| ------------------------- | ------------- | -------------------------- |
| Плотная, 0 0 60 80        | 60×80         | \[40, 80]                  |
| Плотная, 0 0 60 80        | 90×120        | \[60, 120]                 |
| С полями, -20 -10 100 120 | 60×80         | \[36, 58]                  |
| С полями, -20 -10 100 120 | 90×120        | \[54, 87]                  |

Эти расчёты относятся к нашему изображению без преобразований и явному центральному `meet`. Другое выравнивание, `slice` или `none` требует соответствующего преобразования координат. Наше [руководство по пропорциям](https://perfectvector.com/ru/blog/svg-preserveaspectratio) описывает эти варианты. Дополнительные преобразования CSS изображения маркера тоже нуждаются в отдельной проверке.

## Проверьте размещение на карте без тайлов

Мы использовали Leaflet 1.9.4 — стабильный выпуск, указанный на [официальной странице загрузки](https://leafletjs.com/download.html). Эксперимент локально загружает JavaScript и CSS закреплённой версии и не добавляет тайловый слой. Каждая карта проецирует координату `[0, 0]` на синее перекрестие и показывает одно SVG-изображение рядом с этой опорной точкой.

Проверка исходников нашла один одинаковый составной контур и отсутствие встроенного растра в каждом SVG. Размеры изображения в браузере были 60×80 и 90×120 пикселей, как запрошено. Используя наблюдаемый прямоугольник каждого изображения, мы сравнили рассчитанную позицию кончика с проецируемой географической точкой:

| Исходник | Размер значка | Ошибка при предполагаемом якоре внизу по центру | Ошибка с рассчитанным якорем кончика |
| -------- | ------------- | ----------------------------------------------- | ------------------------------------ |
| Плотный  | 60×80         | 10 px вправо                                    | 0 px                                 |
| Плотный  | 90×120        | 15 px вправо                                    | 0 px                                 |
| С полями | 60×80         | 6 px вправо, 22 px вверх                        | 0 px                                 |
| С полями | 90×120        | 9 px вправо, 33 px вверх                        | 0 px                                 |

Перекрестие также визуально совпало с исправленными кончиками меток. Это наблюдения нашего тестового примера в одном Chrome, а не гарантия для любого SVG, браузера или интеграции карты. Метка с полями после исправления якоря остаётся меньше внутри того же прямоугольника изображения. Размещение и размер графики — отдельные решения.

## Скопируйте рабочий стартовый пример маркера

Сохраните этот полный HTML как `marker.html` и раздавайте локальным веб-сервером. Он загружает закреплённые файлы Leaflet из CDN, на который ссылается страница загрузки Leaflet. SVG закодирован в URL изображения, поэтому примеру не нужен файл значка или сервис тайлов. Для двух файлов библиотеки нужен доступ в интернет.

```html
<!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>
```

Показанное состояние должно сообщить `anchor: [36, 58]`, размер изображения `[60, 80]` и нулевое смещение. Измените `size` на `[90, 120]`: тот же расчёт даст `[54, 87]`. Чтобы использовать плотную рамку, измените и `viewBox` SVG, и массив JavaScript `viewBox` на `0 0 60 80`. Они должны совпадать.

Контур включает круглое внутреннее очертание с `fill-rule="evenodd"`, создавая настоящее отверстие. Правило объясняет [справочник MDN по fill-rule](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/fill-rule). Пример не добавляет границу или преобразование контейнеру карты; они потребовали бы осторожности при сравнении прямоугольников области просмотра с координатами контейнера.

## Проверьте целевую среду перед изменением графики

Если маркер всё ещё не попадает в координату, проверяйте по порядку:

1. Подтвердите загрузку SVG-изображения и нужную пиксельную ширину и высоту элемента маркера.
2. Найдите настоящий исходный кончик с учётом преобразований внутри SVG.
3. Проверьте полный viewBox, его минимальные координаты и режим вписывания.
4. Пересчитывайте якорь при изменении рамки или размера значка.
5. На время теста удалите посторонние переопределения CSS, затем ещё раз проверьте конечное оформление и целевые размеры отображения.

При этой проверке фиксируйте географическую координату. Её перемещение для компенсации полей изображения скроет ошибку размещения графики.

Это процесс браузерного маркера. [SVG-маркер QGIS](https://perfectvector.com/ru/blog/qgis-svg-markers) использует собственные настройки целевой среды и параметризованный синтаксис окрашивания; сохраняйте обычный мастер SVG для общего браузерного использования.

Если значок существует только в PNG или JPG, а нужны редактируемые контуры, [преобразуйте графику с PerfectVector](https://perfectvector.com/ru/image-to-vector), проверьте отверстие и кончик и сохраните чистый мастер SVG. Затем рассчитайте якорь Leaflet из конечной исходной рамки. Векторизация готовит графику; код карты задаёт местоположения и размещение. Если проблема лишь в смещении существующего SVG, исправляйте рамку или якорь напрямую. [Руководство по векторизации изображений](https://perfectvector.com/ru/blog/what-is-image-vectorization) объясняет, когда полезно преобразование растрового исходника.

## Частые вопросы

**iconAnchor — это широта и долгота?**
Нет. Это пиксельная позиция внутри показанного изображения значка, измеренная от его верхнего левого угла. Географическая позиция маркера задаётся отдельно.

**Почему SVG с полями остаётся маленьким после исправления якоря?**
Пустая исходная рамка по-прежнему занимает часть прямоугольника изображения. Исправление якоря размещает кончик; оно не удаляет пустое место и не увеличивает закрашенную фигуру.

**Нужно ли всегда использовать половину ширины и полную высоту?**
Используйте эту пару только тогда, когда видимая точка прикрепления действительно там. Нашим асимметричной метке и исходнику с полями нужны другие значения, хотя оба — графика маркеров местоположения.

**Исправляет ли преобразование PNG в SVG местоположение маркера?**
Преобразование может дать редактируемую графику. Конечная исходная рамка, показываемые размеры, якорь и координаты карты по-прежнему определяют размещение маркера.

## Источники

1. [Leaflet — Справочник Icon](https://leafletjs.com/reference.html#icon) — Пиксельные размеры, начало отсчёта якоря, стандартное центрирование и географическое совмещение.
2. [Leaflet — Маркеры с собственными значками](https://leafletjs.com/examples/custom-icons/) — Создание значка и назначение его маркеру.
3. [Leaflet — Загрузка](https://leafletjs.com/download.html) — Стабильный выпуск и ссылки на его закреплённые JavaScript и CSS.
4. [MDN — viewBox](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/viewBox) — Прямоугольник исходных координат для двух рамок.
5. [MDN — preserveAspectRatio](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/preserveAspectRatio) — Равномерное вписывание и центрирование свободного места для xMidYMid meet.
6. [MDN — fill-rule](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/fill-rule) — Чётно-нечётное правило для прозрачного отверстия.

## Explore

- [Read the HTML article](https://perfectvector.com/ru/blog/leaflet-svg-marker-anchor)
- [SVG в узлах Cytoscape.js: вписывание и отступы](https://perfectvector.com/ru/blog/cytoscape-svg-node-artwork.md)
- [Измеряйте длину контура SVG, не путая масштаб](https://perfectvector.com/ru/blog/measure-svg-path-length.md)
- [Product facts](https://perfectvector.com/product-facts.md)
- [FAQ](https://perfectvector.com/faq.md)
- [Pricing and credits](https://perfectvector.com/pricing.md)
