В библиотеке Leaflet элементы управления (controls) представляют
собой специализированные UI-компоненты, интегрированные в карту и
размещаемые в фиксированных позициях поверх тайлового слоя. Все
стандартные элементы, такие как масштабирование или переключение слоёв,
построены на основе общего абстрактного класса
L.Control.
Расширение этого класса позволяет создавать собственные интерактивные панели, кнопки, переключатели и комплексные интерфейсы, тесно связанные с состоянием карты.
L.Control является базовым строительным блоком для всех
пользовательских элементов интерфейса. Он отвечает за:
Основной принцип: контрол не управляет картой напрямую, он лишь взаимодействует с ней через API Leaflet.
При расширении L.Control ключевыми методами
являются:
onAdd(map)Метод вызывается при добавлении контроля на карту.
Обязанности:
L.Control.MyControl = L.Control.extend({
onAdd: function (map) {
const container = L.DomUtil.create('div', 'leaflet-bar custom-control');
container.innerHTML = 'Кнопка';
return container;
}
});
onRemove(map)Вызывается при удалении контроля с карты.
Используется для:
onRemove: function (map) {
// очистка ресурсов
}
Базовая структура расширения L.Control строится через
L.Control.extend.
L.Control.ButtonControl = L.Control.extend({
options: {
position: 'topright'
},
onAdd: function (map) {
const container = L.DomUtil.create('div', 'leaflet-bar custom-button');
container.innerHTML = 'OK';
L.DomEvent.on(container, 'click', this._onClick, this);
this._map = map;
return container;
},
onRemove: function () {
// освобождение обработчиков
},
_onClick: function () {
alert('Нажато');
}
});
Leaflet использует паттерн фабрики для удобного создания экземпляров:
L.control.buttonControl = function (opts) {
return new L.Control.ButtonControl(opts);
};
Использование:
L.control.buttonControl({ position: 'topleft' }).addTo(map);
Leaflet поддерживает четыре базовые позиции:
toplefttoprightbottomleftbottomrightПри расширении класса позиция задаётся через
options.
options: {
position: 'bottomleft'
}
Внутренне Leaflet группирует контролы по контейнерам, создавая отдельные DOM-блоки для каждой позиции.
Leaflet предоставляет утилиты L.DomUtil и
L.DomEvent для безопасного взаимодействия с DOM.
const container = L.DomUtil.create('div', 'custom-control');
При создании UI поверх карты важно блокировать события:
L.DomEvent.disableClickPropagation(container);
L.DomEvent.disableScrollPropagation(container);
Это предотвращает:
Контрол получает доступ к карте через параметр map в
onAdd.
onAdd: function (map) {
this._map = map;
}
Через него можно:
this._map.setView([51.505, -0.09], 13);
Контрол может изменять своё визуальное состояние в зависимости от карты.
L.Control.ZoomDisplay = L.Control.extend({
onAdd: function (map) {
this._container = L.DomUtil.create('div', 'zoom-display');
this._container.innerHTML = map.getZoom();
map.on('zoomend', this._update, this);
return this._container;
},
onRemove: function (map) {
map.off('zoomend', this._update, this);
},
_update: function () {
this._container.innerHTML = this._map.getZoom();
}
});
Ключевой принцип: контрол подписывается на события карты и синхронизирует UI.
Частый сценарий — переключение режима.
L.Control.Toggle = L.Control.extend({
options: {
position: 'topright'
},
onAdd: function (map) {
this._active = false;
const container = L.DomUtil.create('div', 'leaflet-bar toggle-control');
this._button = L.DomUtil.create('a', '', container);
this._button.href = '#';
this._button.innerHTML = 'OFF';
L.DomEvent.on(this._button, 'click', this._toggle, this);
return container;
},
_toggle: function (e) {
L.DomEvent.preventDefault(e);
this._active = !this._active;
this._button.innerHTML = this._active ? 'ON' : 'OFF';
}
});
Контролы часто служат мостом между UI и логикой карты.
Пример: включение режима рисования или добавления маркеров.
_onClick: function () {
this._map.on('click', this._addMarker, this);
},
_addMarker: function (e) {
L.marker(e.latlng).addTo(this._map);
}
Важно контролировать жизненный цикл обработчиков, чтобы не оставлять активные подписки.
Контрол может изменять DOM в зависимости от состояния приложения.
setEnabled: function (enabled) {
if (enabled) {
this._container.classList.add('active');
} else {
this._container.classList.remove('active');
}
}
Такой подход используется для:
Более сложные интерфейсы могут содержать несколько элементов:
onAdd: function () {
const container = L.DomUtil.create('div', 'complex-control');
const btn1 = L.DomUtil.create('button', '', container);
btn1.innerHTML = 'A';
const btn2 = L.DomUtil.create('button', '', container);
btn2.innerHTML = 'B';
L.DomEvent.on(btn1, 'click', this._actionA, this);
L.DomEvent.on(btn2, 'click', this._actionB, this);
return container;
}
Leaflet не ограничивает стилизацию. Контролы полностью управляются через CSS-классы.
.custom-control {
background: white;
padding: 6px;
border-radius: 4px;
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
}
Стили должны учитывать:
Leaflet автоматически добавляет контролы в контейнер
.leaflet-control-container. Важно учитывать:
Контролы удобно оформлять как независимые модули:
export const MyControl = L.Control.extend({ ... });
export function myControl(options) {
return new MyControl(options);
}
Такой подход обеспечивает:
Контролы могут обмениваться состоянием через карту:
map.myState = { mode: 'edit' };
Или через события:
map.fire('modechange', { mode: 'edit' });
map.on('modechange', handler);
Это позволяет строить сложные интерфейсы без жёсткой связности компонентов.
onRemove;L.DomEvent.disableClickPropagation;Контрол может включать:
Некоторые контролы выполняют роль интерфейса к данным:
Контрол может работать с API:
fetch('/api/data')
.then(r => r.json())
.then(data => {
this._render(data);
});
Важно учитывать:
При росте приложения контролы становятся частью архитектурного слоя:
Разделение по ответственности упрощает поддержку и расширение системы.