В Mapbox GL JS управление элементами интерфейса карты строится вокруг
единого контрактного подхода. Любой пользовательский элемент управления
реализует интерфейс IControl, который определяет жизненный
цикл контролов и их интеграцию в карту.
Основная идея заключается в том, что карта не зависит от конкретной реализации UI-компонентов. Любой элемент — масштабирование, кнопка, переключатель слоя или кастомная панель — подключается через единый интерфейс.
IControl определяет поведение пользовательских
контролов, которые добавляются в интерфейс карты. Через него
обеспечивается:
Контролы не работают напрямую с внутренними слоями рендеринга Mapbox GL JS. Они взаимодействуют только через публичный API карты.
Интерфейс задаёт минимальный набор методов, необходимых для интеграции пользовательского элемента:
onAdd(map)onRemove(map)Дополнительно могут использоваться вспомогательные свойства, влияющие на позиционирование.
Метод onAdd вызывается при добавлении контрола на
карту.
onAdd(map: Map): HTMLElement
map.addControl(control);map.class ZoomInfoControl {
onAdd(map) {
this._map = map;
this._container = document.createElement('div');
this._container.className = 'mapboxgl-ctrl zoom-info';
this._container.textContent = `Zoom: ${map.getZoom().toFixed(2)}`;
this._map.on('zoom', this._update.bind(this));
return this._container;
}
_update() {
this._container.textContent = `Zoom: ${this._map.getZoom().toFixed(2)}`;
}
}
Метод onRemove вызывается при удалении контрола с
карты.
onRemove(map: Map): void
class ZoomInfoControl {
onRemove() {
this._map.off('zoom', this._update.bind(this));
this._container.parentNode.removeChild(this._container);
this._map = undefined;
}
}
DOM-элемент, возвращаемый из onAdd, автоматически
помещается в стандартный контейнер Mapbox GL JS.
Контейнер:
Mapbox GL JS поддерживает четыре стандартные позиции:
top-lefttop-rightbottom-leftbottom-rightПри добавлении контрола позиция может быть указана вторым параметром:
map.addControl(new ZoomInfoControl(), 'top-right');
Если позиция не задана, используется значение по умолчанию, определяемое самим контролом.
Некоторые реализации контролов определяют метод
getDefaultPosition, который задаёт стандартное расположение
при отсутствии явного указания.
getDefaultPosition(): string
top-lefttop-rightbottom-leftbottom-rightclass CompassControl {
getDefaultPosition() {
return 'top-right';
}
}
Контрол может объединять все элементы интерфейса
IControl.
class LayerToggleControl {
constructor(layers) {
this._layers = layers;
}
onAdd(map) {
this._map = map;
this._container = document.createElement('div');
this._container.className = 'mapboxgl-ctrl layer-toggle';
this._layers.forEach(layer => {
const button = document.createElement('button');
button.textContent = layer.name;
button.addEventListener('click', () => {
const visibility = map.getLayoutProperty(layer.id, 'visibility');
map.setLayoutProperty(
layer.id,
'visibility',
visibility === 'visible' ? 'none' : 'visible'
);
});
this._container.appendChild(button);
});
return this._container;
}
onRemove() {
this._container.innerHTML = '';
this._map = undefined;
}
getDefaultPosition() {
return 'top-left';
}
}
Контролы, реализующие IControl, участвуют в жизненном
цикле карты косвенно:
map.addControl;map.removeControl;Удаление контрола должно сопровождаться полной очисткой всех привязанных обработчиков, поскольку Mapbox GL JS не управляет внутренними зависимостями пользовательских компонентов.
Контейнеры контролов используют стандартные CSS-классы Mapbox:
mapboxgl-ctrlmapboxgl-ctrl-groupДополнительные классы задаются вручную:
.mapboxgl-ctrl.layer-toggle {
display: flex;
flex-direction: column;
gap: 4px;
}
.mapboxgl-ctrl.layer-toggle button {
background: white;
border: none;
padding: 6px 10px;
cursor: pointer;
}
Стили не управляются библиотекой и полностью определяются разработчиком.
В TypeScript интерфейс описывается следующим образом:
interface IControl {
onAdd(map: Map): HTMLElement;
onRemove(map: Map): void;
getDefaultPosition?(): string;
}
Типизация обеспечивает корректную интеграцию пользовательских контролов в систему Mapbox GL JS и предотвращает ошибки при реализации обязательных методов.
Несколько контролов могут быть добавлены в один контейнер позиции. В этом случае:
Архитектура интерфейса накладывает ряд ограничений:
Эти ограничения компенсируются гибкостью реализации пользовательских компонентов и полной свободой в работе с DOM и API карты.