Позиционирование контролов

Контролы в Leaflet размещаются поверх карты в фиксированных областях интерфейса и привязываются не к географическим координатам, а к экранным углам контейнера карты. Основной механизм позиционирования основан на предопределённых зонах: topleft, topright, bottomleft, bottomright, которые определяют, в какой части DOM-контейнера будет находиться элемент управления.

Каждый контрол в Leaflet реализуется через систему L.Control и при добавлении на карту получает контейнер DOM-элемент, который помещается в один из четырёх слоёв:

  • верхний левый угол
  • верхний правый угол
  • нижний левый угол
  • нижний правый угол

Эти зоны соответствуют CSS-контейнерам внутри карты:

  • .leaflet-top
  • .leaflet-bottom
  • .leaflet-left
  • .leaflet-right

Комбинация этих классов формирует итоговую позицию, например:

  • .leaflet-top .leaflet-lefttopleft
  • .leaflet-top .leaflet-righttopright

Каждый контрол автоматически инжектируется в соответствующий контейнер и получает вертикальное и горизонтальное выравнивание через CSS.

Задание позиции при создании контролов

Позиция задаётся через опцию position в конструкторе:

L.control({ position: 'topright' }).addTo(map);

Возможные значения строго ограничены четырьмя вариантами:

  • topleft
  • topright
  • bottomleft
  • bottomright

Любое другое значение игнорируется и приводит к использованию значения по умолчанию (topright для большинства встроенных контролов).

При создании стандартных контролов поведение аналогично:

L.control.zoom({ position: 'bottomright' }).addTo(map);
L.control.scale({ position: 'bottomleft' }).addTo(map);

Внутренняя структура контейнеров карты

Контейнер карты формируется как набор вложенных DOM-слоёв:

.leaflet-map-pane
  ├── .leaflet-tile-pane
  ├── .leaflet-shadow-pane
  ├── .leaflet-overlay-pane
  ├── .leaflet-marker-pane
  ├── .leaflet-tooltip-pane
  └── .leaflet-control-container
        ├── .leaflet-top
        │     ├── .leaflet-left
        │     └── .leaflet-right
        └── .leaflet-bottom
              ├── .leaflet-left
              └── .leaflet-right

Все контролы добавляются именно в .leaflet-control-container, а затем распределяются по соответствующим зонам.

Порядок контролов внутри одной позиции

Если несколько контролов размещены в одной зоне, их порядок зависит от порядка добавления на карту:

L.control.zoom({ position: 'topright' }).addTo(map);
L.control.layers(null, null, { position: 'topright' }).addTo(map);

Контрол, добавленный позже, визуально окажется ниже в вертикальном стеке (или правее/левее в зависимости от CSS-направления).

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

  • сначала добавленные элементы → выше
  • позже добавленные элементы → ниже

CSS использует flex или block потоковое расположение внутри контейнеров.

Стилизация и смещение контролов

Каждый контрол обёрнут в элемент с классом leaflet-control, который имеет базовые стили:

  • margin
  • box-shadow
  • border-radius
  • pointer-events: auto

Группы контролов внутри угла получают дополнительные контейнеры:

  • .leaflet-bar — для кнопочных контролов (zoom)
  • .leaflet-control-layers — для слоёв

Отступ между контролами задаётся через CSS:

.leaflet-top .leaflet-left .leaflet-control {
    margin-top: 10px;
    margin-left: 10px;
}

Изменение этих значений позволяет управлять плотностью интерфейса.

Перемещение встроенных контролов

Встроенные контролы можно переопределять через опции карты:

const map = L.map('map', {
    zoomControl: false,
    attributionControl: false
});

После отключения их можно создать заново с нужной позицией:

L.control.zoom({ position: 'bottomright' }).addTo(map);

Это позволяет полностью перестроить интерфейс управления.

Пользовательские контролы и их позиционирование

Любой кастомный контрол наследуется от L.Control и обязан реализовать метод onAdd:

const CustomControl = L.Control.extend({
    onAdd: function(map) {
        const div = L.DomUtil.create('div', 'custom-control');
        div.innerHTML = 'Кнопка';
        return div;
    }
});

Добавление с позиционированием:

L.control.customControl = function(opts) {
    return new CustomControl(opts);
};

L.control.customControl({ position: 'topright' }).addTo(map);

Позиционирование работает автоматически через механизм базового класса.

Поведение при изменении размеров карты

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

  • .leaflet-control-container пересчитывает размеры
  • контролы остаются привязанными к углам
  • позиция не пересчитывается вручную

CSS обеспечивает адаптацию без участия JavaScript.

Глубокая кастомизация через DOM

Контейнеры контролов доступны через API карты:

map.getContainer().querySelector('.leaflet-control-container');

Каждый контрол имеет ссылку на DOM-элемент:

control.getContainer();

Это позволяет динамически изменять стили:

control.getContainer().style.marginTop = '20px';

Управление слоями перекрытия

Контролы располагаются поверх всех слоёв карты благодаря z-index:

  • .leaflet-pane — слои карты
  • .leaflet-control-container — UI поверх карты

Контейнер контролов всегда имеет больший приоритет, чем тайлы и маркеры, что обеспечивает стабильную видимость интерфейса.

Конфликты позиционирования и их разрешение

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

  • вертикальный стек в левых/правых зонах
  • горизонтальное выравнивание внутри баров

Для предотвращения наложений применяется:

  • увеличение margin
  • разделение по разным углам
  • создание группирующих контейнеров

Пользовательские зоны позиционирования через CSS

Хотя Leaflet ограничивает API четырьмя позициями, визуально можно создавать дополнительные зоны через CSS-переопределения:

.leaflet-custom-top-center {
    position: absolute;
    top: 10px;
    left: 50%;
    transform: translateX(-50%);
}

И подключение контролов вне стандартной системы:

const div = L.DomUtil.create('div', 'leaflet-control leaflet-custom-top-center');
map.getContainer().appendChild(div);

Такой подход полностью обходится без встроенного механизма L.Control.

Слои контролов и приоритет отображения

Контролы делятся на логические группы:

  • навигационные (zoom)
  • информационные (scale, attribution)
  • функциональные (layers, custom buttons)

Каждый тип может занимать отдельную зону для снижения визуальной нагрузки интерфейса.

Поведение на мобильных устройствах

На мобильных экранах контейнеры автоматически сжимаются:

  • контролы уменьшаются по размеру
  • увеличиваются отступы для кликабельности
  • возможен перенос элементов внутри стека

CSS медиазапросы Leaflet обеспечивают адаптивность без дополнительного кода.

Динамическое изменение позиции

Позиция контролов может быть изменена после добавления:

control.setPosition('bottomleft');

При этом:

  • DOM-элемент удаляется из текущего контейнера
  • добавляется в новый контейнер
  • сохраняются события и состояние

Это позволяет перестраивать интерфейс без пересоздания контролов.