В Leaflet позиционирование элементов управления строится вокруг концепции «угловых контейнеров». Карта разделяет интерфейс на четыре фиксированные области, в которые размещаются все стандартные и пользовательские контролы: верхний левый, верхний правый, нижний левый и нижний правый углы. Эти области не являются произвольными координатами — они реализованы через DOM-контейнеры с предсказуемой структурой и CSS-правилами.
Каждый контрол в Leaflet при добавлении на карту попадает в один из четырёх контейнеров:
toplefttoprightbottomleftbottomrightЭти значения задаются через опцию position при создании
контрола:
L.control.zoom({
position: 'topright'
}).addTo(map);
Внутри библиотеки каждому значению соответствует DOM-узел вида:
.leaflet-top.leaflet-left.leaflet-top.leaflet-right.leaflet-bottom.leaflet-left.leaflet-bottom.leaflet-rightLeaflet не использует абсолютные координаты для отдельных контролов — только контейнерную модель.
Контейнер карты содержит четыре основных блока:
<div class="leaflet-control-container">
<div class="leaflet-top leaflet-left"></div>
<div class="leaflet-top leaflet-right"></div>
<div class="leaflet-bottom leaflet-left"></div>
<div class="leaflet-bottom leaflet-right"></div>
</div>
Каждый добавленный контрол вставляется внутрь соответствующего блока. Порядок внутри блока определяет вертикальное расположение элементов.
Контролы, добавленные в один и тот же угол, выстраиваются последовательно сверху вниз (или снизу вверх в зависимости от CSS направления).
L.control.scale({ position: 'bottomleft' }).addTo(map);
L.control.zoom({ position: 'bottomleft' }).addTo(map);
L.control.layers(baseLayers, overlays, { position: 'bottomleft' }).addTo(map);
Результат: элементы будут расположены друг под другом в порядке добавления.
Факторы, влияющие на порядок:
.addTo(map)z-index контроловmargin, padding)Leaflet использует фиксированное позиционирование через CSS:
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-right {
right: 0;
}
Каждый контрол внутри контейнера имеет:
.leaflet-control {
pointer-events: auto;
margin: 10px;
clear: both;
}
Ключевой момент: контейнеры отключают события мыши, а сами контролы их включают обратно. Это позволяет карте оставаться интерактивной под элементами управления.
Любой пользовательский контрол наследуется от L.Control
и получает стандартную систему позиционирования.
L.Control.CustomButton = L.Control.extend({
options: {
position: 'topright'
},
onAdd: function(map) {
const container = L.DomUtil.create('div', 'leaflet-bar custom-button');
container.innerHTML = 'Click';
L.DomEvent.disableClickPropagation(container);
return container;
}
});
L.control.customButton = function(opts) {
return new L.Control.CustomButton(opts);
};
L.control.customButton({ position: 'topright' }).addTo(map);
Опция position автоматически определяет, в какой
контейнер попадёт контрол.
Порядок контролов регулируется не только добавлением, но и DOM-манипуляцией:
const controlA = L.control({ position: 'topright' });
const controlB = L.control({ position: 'topright' });
controlA.on Add = function() {
const div = L.DomUtil.create('div', 'box-a');
div.innerHTML = 'A';
return div;
};
controlB.on Add = function() {
const div = L.DomUtil.create('div', 'box-b');
div.innerHTML = 'B';
return div;
};
controlB.addTo(map);
controlA.addTo(map);
Даже при обратном порядке добавления можно изменить визуальную последовательность через DOM:
const container = map.getContainer();
const rightTop = container.querySelector('.leaflet-top.leaflet-right');
rightTop.appendChild(rightTop.firstChild);
Leaflet по умолчанию использует фиксированные позиции:
toprightbottomrighttopright или
bottomright (зависит от реализации)bottomleft или
bottomrightПример настройки стандартных контролов:
const map = L.map('map', {
zoomControl: false,
attributionControl: false
});
L.control.zoom({
position: 'bottomright'
}).addTo(map);
L.control.attribution({
position: 'bottomleft'
}).addTo(map);
Контролы Leaflet располагаются выше слоёв карты за счёт
z-index: 1000. Однако разные контролы могут перекрывать
друг друга, если:
Пример повышения приоритета:
.custom-control {
z-index: 1100;
}
Контейнер контролов привязан к элементу карты:
map.getContainer()
Внутри него создаётся структура контролов при инициализации. Прямое изменение DOM вне API Leaflet допустимо, но может привести к рассинхронизации состояния библиотеки.
Leaflet не предоставляет официального API для изменения позиции уже добавленного контрола. Однако возможно переинициализировать его:
map.removeControl(control);
control.options.position = 'bottomright';
map.addControl(control);
Такой подход пересоздаёт привязку к другому контейнеру.
Несколько элементов можно объединять логически:
const groupControl = L.Control.extend({
options: { position: 'topright' },
onAdd: function() {
const wrapper = L.DomUtil.create('div', 'control-group');
const btn1 = L.DomUtil.create('button', '', wrapper);
btn1.innerHTML = '1';
const btn2 = L.DomUtil.create('button', '', wrapper);
btn2.innerHTML = '2';
return wrapper;
}
});
L.control.groupControl = opts => new groupControl(opts);
L.control.groupControl().addTo(map);
Такой подход позволяет обходить ограничение угловой модели и создавать собственную компоновку внутри одного угла.
Позиции контролов фиксированы относительно контейнера карты, поэтому при изменении размеров:
При ресайзе контейнера карты:
map.invalidateSize();
контролы автоматически остаются на своих местах без дополнительной логики.
Контейнеры контролов используют отключение событий:
pointer-events: none;
а сами контролы включают их обратно:
pointer-events: auto;
Это создаёт важный эффект: клики проходят через пустые области интерфейса, но блокируются на самих контролах.