Удаление контролов

В MapLibre GL JS контролы представляют собой расширения интерфейса карты, добавляющие интерактивные элементы управления: масштабирование, поворот, геолокацию, масштабную линейку, пользовательские панели. Каждый контрол реализует единый контракт жизненного цикла и подключается к экземпляру карты через addControl, а удаляется через removeControl.

Удаление контролов — это не просто визуальное скрытие элемента, а полноценное отсоединение логики, DOM-узлов и обработчиков событий от экземпляра карты.


Базовый механизм удаления

Контрол удаляется с карты вызовом метода:

map.removeControl(control);

где control — это ранее добавленный объект, реализующий интерфейс IControl.

Ключевой момент: для удаления требуется ссылка на тот же экземпляр контролла, который был добавлен. Попытка удалить новый объект с аналогичной конфигурацией не приведёт к эффекту, так как идентичность контролов определяется по ссылке.


Сохранение ссылки на контрол

Практика управления контролами всегда предполагает сохранение экземпляров:

const navigationControl = new maplibregl.NavigationControl();

map.addControl(navigationControl);

// позже
map.removeControl(navigationControl);

Если ссылка теряется, удалить контрол стандартными средствами невозможно без обходных решений через DOM, что нарушает модель управления библиотекой.


Жизненный цикл IControl

Каждый контрол реализует два ключевых метода:

  • onAdd(map) — вызывается при добавлении
  • onRemove(map) — вызывается при удалении

При вызове removeControl происходит следующая последовательность:

  1. Удаление DOM-элемента контролла из контейнера карты
  2. Вызов control.onRemove(map)
  3. Освобождение внутренних ссылок со стороны карты

Пример структуры пользовательского контролла:

class CustomControl {
  onAdd(map) {
    this._map = map;

    this._container = document.createElement('div');
    this._container.className = 'custom-control';

    this._container.textContent = 'Control';

    return this._container;
  }

  onRemove() {
    this._map = undefined;
    this._container = undefined;
  }
}

Удаление такого контролла автоматически инициирует onRemove, что позволяет корректно освободить ресурсы.


Удаление стандартных контролов

Стандартные контролы MapLibre создаются как отдельные экземпляры и могут быть удалены аналогично пользовательским.

const nav = new maplibregl.NavigationControl();
map.addControl(nav);

// удаление
map.removeControl(nav);

ScaleControl

const scale = new maplibregl.ScaleControl();
map.addControl(scale);

// удаление
map.removeControl(scale);

GeolocateControl

const geolocate = new maplibregl.GeolocateControl();
map.addControl(geolocate);

map.removeControl(geolocate);

Важно учитывать, что контролы, взаимодействующие с геолокацией или событиями устройства, должны корректно завершать активные процессы в onRemove.


Особенности удаления AttributionControl

Контрол атрибуции часто добавляется автоматически при создании карты. Он может быть отключён при инициализации:

const map = new maplibregl.Map({
  container: 'map',
  style: 'style.json',
  attributionControl: false
});

Если контрол добавлен явно:

const attribution = new maplibregl.AttributionControl();
map.addControl(attribution);

то он удаляется стандартным способом:

map.removeControl(attribution);

Следует учитывать, что повторное добавление атрибуции может создавать визуально дублирующиеся элементы, если управление не централизовано.


Ошибки при удалении контролов

Типовые проблемы возникают из-за неправильного управления ссылками и состоянием:

Потеря ссылки на контрол

map.addControl(new maplibregl.NavigationControl());

// удалить невозможно без сохранённой ссылки

Решение заключается в сохранении экземпляра до добавления.


Повторное удаление

Попытка удалить уже удалённый контрол обычно не приводит к критической ошибке, но может вызывать неконсистентность состояния, если внешний код продолжает взаимодействовать с DOM-элементами контролла.


Удаление при отсутствии добавления

Если контрол не был добавлен, вызов removeControl не имеет эффекта. Внутренние проверки MapLibre предотвращают разрушение состояния карты, однако логика приложения может оказаться несогласованной.


Динамическое управление контролами

В интерфейсах с переключаемыми режимами карты контролы часто добавляются и удаляются в зависимости от состояния приложения.

let scaleControl = null;

function enableScale() {
  if (!scaleControl) {
    scaleControl = new maplibregl.ScaleControl();
    map.addControl(scaleControl);
  }
}

function disableScale() {
  if (scaleControl) {
    map.removeControl(scaleControl);
    scaleControl = null;
  }
}

Такой подход обеспечивает предсказуемое управление состоянием и исключает утечки DOM-элементов.


Взаимодействие с позиционированием контролов

Контролы в MapLibre GL JS могут быть размещены в разных углах карты (top-left, top-right, bottom-left, bottom-right). Удаление контролла не зависит от его позиции, однако влияет на перерасчёт layout контейнера.

map.addControl(new maplibregl.NavigationControl(), 'top-right');

Удаление:

map.removeControl(navigationControl);

После удаления контейнер позиции автоматически пересчитывается, и оставшиеся контролы сдвигаются без пустых слотов.


Пользовательские контейнеры и каскадное удаление

При создании сложных интерфейсов контрол может содержать вложенные элементы, таймеры, подписки на события карты.

class PanelControl {
  onAdd(map) {
    this._map = map;

    this._container = document.createElement('div');

    this._handler = () => {
      // обработка события
    };

    map.on('move', this._handler);

    return this._container;
  }

  onRemove() {
    this._map.off('move', this._handler);
    this._handler = null;
    this._container = null;
  }
}

Удаление такого контролла должно гарантировать:

  • отписку от событий карты
  • очистку внутренних ссылок
  • освобождение DOM-структуры

Поведение при уничтожении карты

При вызове:

map.remove();

все добавленные контролы автоматически удаляются. В этом случае removeControl вызывать не требуется, так как карта инициирует полный цикл очистки:

  • удаление всех контролов
  • вызов onRemove для каждого
  • освобождение ресурсов WebGL контекста

Архитектурные аспекты управления контролами

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

  • реестр активных контролов
  • фабрики создания/удаления
  • реактивные состояния интерфейса

Такой подход позволяет синхронизировать UI и состояние карты, избегая ситуаций, когда контрол существует в DOM, но уже не связан с картой.


Порядок удаления и побочные эффекты

Хотя порядок удаления контролов обычно не критичен, в случаях зависимых контролов (например, панель управления слоями и фильтрами) важно соблюдать последовательность:

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

Это предотвращает обращения к уже несуществующим API карты.


Поведение при кастомных ошибках в onRemove

Если onRemove генерирует исключение, удаление контролла может быть частично завершено, при этом DOM-элемент уже будет удалён. Это создаёт ситуацию рассогласованного состояния.

Поэтому внутри onRemove обычно избегаются потенциально опасные операции без проверки наличия ссылок:

onRemove() {
  if (this._map) {
    this._map.off('event', this._handler);
  }
}