Пользовательские иконки

В Leaflet отображение маркеров основано на классе L.Icon, который определяет внешний вид точки на карте через изображение или HTML-разметку. Стандартная иконка, используемая библиотекой, представляет собой набор из трёх изображений: основного маркера, тени и изображения для Retina-дисплеев. Управление этими элементами позволяет полностью переопределять визуальное представление объектов на карте.

Класс L.Icon описывает набор параметров, которые влияют на отрисовку маркера:

  • iconUrl — основной графический файл иконки
  • shadowUrl — изображение тени
  • iconSize — размеры иконки в пикселях
  • shadowSize — размеры тени
  • iconAnchor — точка привязки иконки к координате
  • popupAnchor — смещение всплывающего окна относительно иконки
  • className — CSS-класс для стилизации

Простейшее создание иконки выглядит следующим образом:

const customIcon = L.icon({
  iconUrl: 'marker.png',
  shadowUrl: 'shadow.png',
  iconSize: [32, 32],
  iconAnchor: [16, 32],
  popupAnchor: [0, -32]
});

Каждый параметр влияет на точность позиционирования и визуальную согласованность элементов на карте. Ошибки в iconAnchor часто приводят к смещению маркера относительно координаты.

Привязка иконки к маркеру

После создания иконки она передаётся в объект маркера:

const marker = L.marker([51.505, -0.09], {
  icon: customIcon
}).addTo(map);

Маркер полностью наследует визуальные свойства иконки, но сохраняет географическую привязку через координаты.

Работа с iconAnchor и popupAnchor

Ключевое значение для точного позиционирования имеет iconAnchor. Оно определяет, какая точка изображения будет соответствовать координате на карте.

Например, если иконка имеет размер 32×32 пикселя:

  • [16, 32] — нижний центр (типичный вариант для «капли» маркера)
  • [0, 0] — верхний левый угол
  • [16, 16] — центр изображения

popupAnchor управляет расположением всплывающего окна:

popupAnchor: [0, -32]

Это смещает popup вверх относительно точки маркера, предотвращая перекрытие.

Использование разных иконок для разных объектов

В реальных приложениях часто требуется отображать различные категории объектов с уникальными иконками. Для этого создаётся несколько экземпляров L.Icon:

const restaurantIcon = L.icon({
  iconUrl: 'restaurant.png',
  iconSize: [30, 30],
  iconAnchor: [15, 30]
});

const hotelIcon = L.icon({
  iconUrl: 'hotel.png',
  iconSize: [30, 30],
  iconAnchor: [15, 30]
});

Далее выбор иконки определяется логикой приложения:

function getIcon(type) {
  switch (type) {
    case 'restaurant':
      return restaurantIcon;
    case 'hotel':
      return hotelIcon;
  }
}

Retina-иконки и адаптация под плотные экраны

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

const retinaIcon = L.icon({
  iconUrl: 'marker.png',
  iconRetinaUrl: 'marker@2x.png',
  iconSize: [32, 32],
  iconAnchor: [16, 32]
});

Это позволяет сохранять чёткость изображения на современных экранах без дополнительных изменений логики.

Иконки на основе CSS (L.divIcon)

Помимо изображений, Leaflet поддерживает HTML-иконки через L.divIcon. Такой подход позволяет создавать динамические иконки, стилизуемые через CSS.

const divIcon = L.divIcon({
  className: 'custom-div-icon',
  html: '<div class="marker-circle"></div>',
  iconSize: [20, 20],
  iconAnchor: [10, 10]
});

CSS:

.custom-div-icon .marker-circle {
  width: 20px;
  height: 20px;
  background-color: red;
  border-radius: 50%;
  border: 2px solid white;
}

Преимущество такого подхода заключается в возможности использовать анимации, градиенты и любые CSS-эффекты без необходимости создавать изображения.

Динамические иконки через HTML

L.divIcon позволяет генерировать содержимое программно:

function createNumberIcon(number) {
  return L.divIcon({
    html: `<div class="number-marker">${number}</div>`,
    className: 'number-icon',
    iconSize: [30, 30],
    iconAnchor: [15, 15]
  });
}

Такой подход используется для кластеризации, отображения счётчиков или состояния объектов.

Стилизация через className

Параметр className добавляет CSS-класс к контейнеру иконки:

L.divIcon({
  className: 'my-custom-marker'
});

Это позволяет управлять внешним видом через CSS без изменения JavaScript-логики:

.my-custom-marker {
  background: transparent;
  border: none;
}

Особенно полезно при интеграции с дизайн-системами или UI-фреймворками.

Масштабирование и адаптивные иконки

Для различных масштабов карты часто требуется изменять размер иконки. Это можно реализовать через обработку событий zoom:

map.on('zoomend', function () {
  const zoom = map.getZoom();
  const size = zoom > 10 ? 40 : 25;

  marker.setIcon(L.icon({
    iconUrl: 'marker.png',
    iconSize: [size, size],
    iconAnchor: [size / 2, size]
  }));
});

Такой подход позволяет визуально выделять объекты при приближении карты.

Использование SVG в иконках

SVG обеспечивает масштабируемость без потери качества. Его можно использовать как через iconUrl, так и через divIcon.

Пример с внешним SVG-файлом:

L.icon({
  iconUrl: 'marker.svg',
  iconSize: [32, 32],
  iconAnchor: [16, 32]
});

Вариант с inline SVG:

L.divIcon({
  html: `
    <svg width="24" height="24" viewBox="0 0 24 24">
      <circle cx="12" cy="12" r="10" fill="blue"/>
    </svg>
  `,
  className: '',
  iconSize: [24, 24],
  iconAnchor: [12, 12]
});

SVG особенно полезен для сложных иконок с несколькими слоями.

Переиспользование иконок

Создание множества одинаковых объектов L.icon может быть неэффективным. Обычно иконки создаются один раз и переиспользуются:

const defaultIcon = L.icon({
  iconUrl: 'marker.png',
  iconSize: [32, 32],
  iconAnchor: [16, 32]
});

И затем применяется ко многим маркерам:

L.marker([lat, lng], { icon: defaultIcon }).addTo(map);

Это снижает нагрузку на память и ускоряет рендеринг.

Удаление и замена иконки у существующего маркера

Leaflet позволяет динамически менять иконку уже созданного маркера:

marker.setIcon(newIcon);

Такой механизм используется для отображения состояний объектов: активен, выбран, недоступен.

Частые проблемы при работе с иконками

Неверное позиционирование почти всегда связано с неправильным iconAnchor. При использовании нестандартных изображений важно учитывать реальный центр «точки привязки».

Другая проблема — отсутствие изображения при сборке проекта. В этом случае пути к файлам должны быть корректно настроены относительно итогового бандла.

Также часто возникает несоответствие размеров изображения и параметра iconSize, что приводит к размытию или обрезанию иконки.

Комбинирование иконок с интерактивностью

Иконки в Leaflet часто связаны с событиями:

marker.on('click', function () {
  this.setIcon(activeIcon);
});

Это позволяет визуально отражать взаимодействие пользователя с объектами карты без изменения структуры данных.