Кастомные иконки через CSS

В Leaflet визуальное представление маркеров изначально строится вокруг стандартной иконки, определённой в L.Icon.Default. Она использует набор изображений и CSS-классов, подключаемых библиотекой автоматически. Однако в реальных интерфейсах этого недостаточно: требуется гибкая стилизация под дизайн системы, изменение формы, цвета, динамических состояний и масштабирования.

Основной путь кастомизации в Leaflet делится на два направления:

  • использование L.Icon с собственными изображениями
  • использование L.DivIcon с HTML-разметкой и CSS-стилями

Второй вариант даёт полный контроль над внешним видом и является базой для CSS-ориентированных решений.


DivIcon как основа CSS-иконок

L.DivIcon позволяет заменить изображение маркера на произвольный HTML-элемент. Это превращает маркер в полноценный DOM-блок, который можно стилизовать через CSS.

Простейшая реализация:

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

L.marker([55.75, 37.61], { icon }).addTo(map);

CSS-часть:

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

.marker-pin {
  width: 14px;
  height: 14px;
  background-color: #2b8cff;
  border-radius: 50%;
  position: relative;
  transform: translate(-50%, -50%);
}

Ключевой момент заключается в том, что Leaflet не ограничивает структуру HTML внутри DivIcon, что позволяет строить сложные композиции.


Геометрия маркера и точка привязки

При работе с кастомными иконками критично учитывать iconAnchor. Это точка, которая фиксирует маркер на координате карты.

Типичная ошибка — визуально центрированный элемент, но неверно заданная точка привязки.

Пример корректной настройки:

iconSize: [40, 40],
iconAnchor: [20, 40]

Это означает, что нижняя центральная точка иконки совпадает с координатой на карте.


Создание маркера в форме пина

Классический геомаркер часто имитирует форму капли. Это достигается исключительно CSS.

.marker-pin {
  width: 20px;
  height: 20px;
  background: #ff4d4f;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  position: relative;
}

.marker-pin::after {
  content: '';
  width: 8px;
  height: 8px;
  background: white;
  position: absolute;
  top: 6px;
  left: 6px;
  border-radius: 50%;
}

Такая реализация позволяет отказаться от изображений и сохранить масштабируемость.


Использование псевдоэлементов для деталей

Псевдоэлементы ::before и ::after применяются для добавления декоративных слоёв: теней, обводок, бликов.

.marker-pin::before {
  content: '';
  position: absolute;
  inset: -4px;
  background: rgba(0, 0, 0, 0.15);
  border-radius: 50%;
  z-index: -1;
}

Это создаёт эффект объёма без использования графики.


Анимация состояния маркера

CSS позволяет добавлять динамику при взаимодействии с картой: hover, active, selection.

.marker-pin {
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.marker-pin:hover {
  transform: scale(1.2) rotate(-45deg);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
}

При большом количестве маркеров важно учитывать производительность: анимации должны быть ограничены свойствами transform и opacity.


SVG через CSS как альтернатива изображениям

SVG может быть встроен в DivIcon напрямую или через background-image.

html: `
  <div class="svg-marker">
    <svg viewBox="0 0 24 24">
      <path d="M12 2C8 2 5 5 5 9c0 5 7 13 7 13s7-8 7-13c0-4-3-7-7-7z"/>
    </svg>
  </div>
`

CSS:

.svg-marker svg {
  width: 32px;
  height: 32px;
  fill: #3a86ff;
}

SVG предпочтителен для масштабирования на разных DPI и для сложных форм.


Использование CSS-спрайтов

При большом количестве маркеров и ограничении на HTTP-запросы применяется спрайтовая техника:

.marker-icon {
  width: 32px;
  height: 32px;
  background-image: url('sprites.png');
  background-position: -64px -32px;
}

Такой подход снижает количество загрузок, но усложняет поддержку и адаптацию под разные разрешения.


Работа с состояниями через классы Leaflet

Leaflet добавляет системные классы к DOM-элементам маркеров:

  • leaflet-marker-icon
  • leaflet-div-icon

Эти классы используются для глобальной стилизации:

.leaflet-div-icon {
  background: transparent;
  border: none;
}

Дополнительно можно вводить собственные состояния:

const icon = L.divIcon({
  className: 'custom-marker status-active',
  html: '<div class="marker-pin"></div>'
});
.status-active .marker-pin {
  background-color: #00c853;
}

Масштабирование и адаптация под zoom

CSS позволяет адаптировать размер маркеров под уровень масштабирования карты. Обычно это решается через классы или динамическое обновление.

.zoom-small .marker-pin {
  transform: scale(0.7) rotate(-45deg);
}

.zoom-large .marker-pin {
  transform: scale(1.4) rotate(-45deg);
}

Класс карты обновляется через обработчик:

map.on('zoomend', () => {
  const zoom = map.getZoom();
  const container = map.getContainer();

  container.classList.toggle('zoom-large', zoom > 12);
  container.classList.toggle('zoom-small', zoom <= 12);
});

Тени и глубина визуализации

Эффект «поднятого» маркера создаётся через box-shadow и псевдоэлементы:

.marker-pin {
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
}

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


Интерактивные маркеры с HTML-структурой

DivIcon позволяет строить сложные компоненты:

html: `
  <div class="card-marker">
    <div class="title">Объект</div>
    <div class="value">42</div>
  </div>
`
.card-marker {
  padding: 6px 10px;
  background: white;
  border-radius: 6px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
  font-size: 12px;
}

Такой подход превращает маркер в мини-виджет.


Оптимизация производительности CSS-маркеров

При большом количестве объектов на карте важны следующие ограничения:

  • минимизация DOM-структуры внутри DivIcon
  • отказ от сложных анимаций filter и blur
  • использование transform вместо изменения размеров через width/height
  • избегание частых перерасчётов layout

Особенно критично при кластерах и динамической загрузке точек.


Поддержка Retina-дисплеев

Для изображений в CSS и SVG важно учитывать плотность пикселей:

@media (-webkit-min-device-pixel-ratio: 2) {
  .marker-pin {
    transform: scale(1.1);
  }
}

Для спрайтов применяются двойные изображения (@2x) и корректировка background-size.


Слои визуальной иерархии маркеров

Leaflet использует z-index для управления порядком отображения:

  • обычные маркеры выше тайлов
  • активные маркеры можно поднимать вручную
.leaflet-marker-icon {
  z-index: 1000;
}

.marker-active {
  z-index: 2000;
}

Это важно при перекрытии объектов на плотных участках карты.