В Leaflet визуальное представление маркеров изначально строится
вокруг стандартной иконки, определённой в L.Icon.Default.
Она использует набор изображений и CSS-классов, подключаемых библиотекой
автоматически. Однако в реальных интерфейсах этого недостаточно:
требуется гибкая стилизация под дизайн системы, изменение формы, цвета,
динамических состояний и масштабирования.
Основной путь кастомизации в Leaflet делится на два направления:
L.Icon с собственными изображениямиL.DivIcon с HTML-разметкой и
CSS-стилямиВторой вариант даёт полный контроль над внешним видом и является базой для 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 может быть встроен в 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 и для сложных форм.
При большом количестве маркеров и ограничении на HTTP-запросы применяется спрайтовая техника:
.marker-icon {
width: 32px;
height: 32px;
background-image: url('sprites.png');
background-position: -64px -32px;
}
Такой подход снижает количество загрузок, но усложняет поддержку и адаптацию под разные разрешения.
Leaflet добавляет системные классы к DOM-элементам маркеров:
leaflet-marker-iconleaflet-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;
}
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);
}
Комбинация теней и градиентов заменяет сложные изображения и улучшает читаемость на карте.
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;
}
Такой подход превращает маркер в мини-виджет.
При большом количестве объектов на карте важны следующие ограничения:
DivIconfilter и
blurtransform вместо изменения размеров через
width/heightОсобенно критично при кластерах и динамической загрузке точек.
Для изображений в 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;
}
Это важно при перекрытии объектов на плотных участках карты.