L.DivIcon представляет собой расширение стандартной
иконки маркера, позволяющее использовать произвольную HTML-разметку
вместо изображения. В отличие от L.Icon, который ограничен
картинками и спрайтами, DivIcon рендерится как DOM-элемент,
что открывает доступ к полноценной стилизации через CSS, динамическому
контенту и интерактивным элементам.
Основная особенность заключается в том, что маркер становится обычным
div-элементом внутри карты, сохраняя при этом все механизмы
позиционирования Leaflet.
Минимальная конфигурация DivIcon строится вокруг
HTML-строки:
const divIcon = L.divIcon({
html: '<div class="custom-marker"></div>',
className: 'custom-icon',
iconSize: [30, 30]
});
L.marker([55.75, 37.61], { icon: divIcon }).addTo(map);
В этом примере:
html задаёт внутреннюю разметку маркераclassName добавляет класс к обёртке LeafleticonSize определяет размеры области, занимаемой
иконкойLeaflet вставляет HTML внутрь контейнера, который затем позиционируется на карте как обычный маркер.
html может содержать любую допустимую HTML-разметку:
L.divIcon({
html: `
<div class="pin">
<span class="pin__circle"></span>
<span class="pin__pulse"></span>
</div>
`,
className: ''
});
Использование пустого className часто применяется для
отключения стандартных стилей Leaflet (leaflet-div-icon),
чтобы полностью контролировать внешний вид.
HTML может формироваться динамически:
function createUserMarker(user) {
return L.divIcon({
html: `
<div class="user-avatar">
<img src="${user.avatar}" />
<span>${user.name}</span>
</div>
`,
className: 'user-marker'
});
}
Определяет размер контейнера:
iconSize: [40, 40]
Если не задан, Leaflet не сможет корректно вычислить смещение центра.
Определяет точку привязки внутри иконки:
iconAnchor: [20, 40]
Для «пина» обычно используется нижний центр.
Смещение всплывающего окна относительно маркера:
popupAnchor: [0, -40]
Позволяет добавлять CSS-классы к контейнеру:
className: 'city-marker highlight'
Если требуется полное отключение стандартной обёртки:
className: ''
HTML-маркеры полностью управляются стилями:
.custom-marker {
width: 20px;
height: 20px;
background: #2a9d8f;
border-radius: 50%;
box-shadow: 0 0 6px rgba(0,0,0,0.3);
}
Для сложных маркеров используется многослойная структура:
.pin {
position: relative;
width: 30px;
height: 30px;
}
.pin__circle {
width: 14px;
height: 14px;
background: #e63946;
border-radius: 50%;
position: absolute;
top: 8px;
left: 8px;
}
.pin__pulse {
position: absolute;
width: 30px;
height: 30px;
background: rgba(230, 57, 70, 0.4);
border-radius: 50%;
animation: pulse 1.5s infinite;
}
DivIcon позволяет обновлять содержимое без пересоздания маркера:
const marker = L.marker([55.75, 37.61], {
icon: L.divIcon({
html: '<div class="counter">0</div>',
className: ''
})
}).addTo(map);
function updateCount(value) {
const el = marker.getElement();
if (!el) return;
el.querySelector('.counter').textContent = value;
}
Доступ к DOM осуществляется через getElement(), что
позволяет напрямую менять структуру внутри маркера.
DivIcon часто применяется для визуализации числовых и категориальных данных:
const stations = [
{ coords: [55.75, 37.61], load: 12 },
{ coords: [55.76, 37.62], load: 87 }
];
stations.forEach(s => {
L.marker(s.coords, {
icon: L.divIcon({
html: `<div class="load ${s.load > 50 ? 'high' : 'low'}">${s.load}</div>`,
className: ''
})
}).addTo(map);
});
Такой подход позволяет кодировать состояние прямо в интерфейсе маркера.
HTML внутри DivIcon может содержать элементы, реагирующие на события:
const icon = L.divIcon({
html: `
<button class="marker-btn">OK</button>
`,
className: ''
});
const marker = L.marker([55.75, 37.61], { icon }).addTo(map);
marker.on('add', () => {
const el = marker.getElement();
el.querySelector('.marker-btn').addEventListener('click', () => {
console.log('click on marker button');
});
});
Leaflet не управляет внутренними событиями DOM, поэтому обработка выполняется вручную.
DivIcon полностью совместим с всплывающими окнами:
marker.bindPopup(`
<div class="popup-content">
<h3>Объект</h3>
<p>Описание точки на карте</p>
</div>
`);
Также поддерживаются tooltip:
marker.bindTooltip('Подсказка', {
direction: 'top'
});
Использование HTML-маркеров увеличивает нагрузку на DOM:
divПри работе с тысячами точек часто используется:
Leaflet.markercluster)L.CircleMarkerL.divIcon({
html: '<div class="num-marker">5</div>',
className: ''
});
L.divIcon({
html: `
<div class="avatar-marker">
<img src="avatar.jpg">
</div>
`,
className: ''
});
L.divIcon({
html: `
<div class="status ${status}">
<span></span>
</div>
`,
className: ''
});
DivIcon масштабируется как обычный DOM-элемент, не завися от географического масштаба. Это создаёт важное отличие от canvas-слоёв:
Неправильное смещение точки привязки приводит к смещению маркера относительно координаты:
iconAnchor: [0, 0] // приводит к "съезду" позиции
Отсутствие iconSize усложняет расчёт центрирования.
Использование сложного DOM без оптимизации вызывает падение производительности на мобильных устройствах.
Обработчики событий, навешанные до появления элемента в DOM, не
срабатывают без привязки к marker.on('add').
DivIcon часто используется как слой представления в более сложных архитектурах:
innerHTMLfunction renderIcon(data) {
return L.divIcon({
html: `<div class="point ${data.type}">${data.value}</div>`,
className: ''
});
}
Такой подход позволяет интегрировать карту в SPA-архитектуры без привязки к Leaflet-рендерингу как к единственному источнику состояния.