Встроенная система контролов в Mapbox GL JS опирается на единый
контракт: любой элемент управления реализует интерфейс
IControl. Этот подход позволяет расширять карту
произвольными UI-компонентами — кнопками, панелями, переключателями
слоёв, поисковыми строками и сложными виджетами, интегрированными в DOM
карты.
Контролы добавляются через метод
map.addControl(control, position), где
position определяет расположение блока интерфейса:
top-lefttop-rightbottom-leftbottom-rightКаждый кастомный контрол представляет собой JavaScript-объект с обязательными методами жизненного цикла.
Минимальная реализация кастомного контрола включает два метода:
onAdd(map) — вызывается при добавлении на картуonRemove() — вызывается при удаленииonAdd обязан вернуть DOM-элемент, который будет вставлен
в интерфейс карты.
class SimpleButtonControl {
onAdd(map) {
this.map = map;
this.container = document.createElement('div');
this.container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';
const button = document.createElement('button');
button.textContent = 'Клик';
button.oncl ick = () => {
this.map.zoomIn();
};
this.container.appendChild(button);
return this.container;
}
onRemove() {
this.container.remove();
this.map = undefined;
}
}
Добавление на карту:
map.addControl(new SimpleButtonControl(), 'top-right');
Mapbox GL JS ожидает, что корневой элемент контрола будет совместим со стандартными CSS-классами:
mapboxgl-ctrl — базовый классmapboxgl-ctrl-group — группа кнопокmapboxgl-ctrl-icon — иконка-кнопкаЭти классы обеспечивают единообразный внешний вид и корректное позиционирование.
this.container = document.createElement('div');
this.container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group custom-control';
Контрол получает доступ к экземпляру карты через onAdd.
Это позволяет:
class FlyToControl {
onAdd(map) {
this.map = map;
this.container = document.createElement('div');
this.container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';
const button = document.createElement('button');
button.textContent = 'Офис';
button.oncl ick = () => {
this.map.flyTo({
center: [37.6173, 55.7558],
zoom: 10
});
};
this.container.appendChild(button);
return this.container;
}
onRemove() {
this.container.remove();
this.map = null;
}
}
Кастомные контролы часто зависят от событий карты. Подписка
осуществляется внутри onAdd.
class MousePositionControl {
onAdd(map) {
this.map = map;
this.container = document.createElement('div');
this.container.className = 'mapboxgl-ctrl';
this.container.textContent = '0, 0';
this.mouseMoveHandler = (e) => {
const lng = e.lngLat.lng.toFixed(4);
const lat = e.lngLat.lat.toFixed(4);
this.container.textContent = `${lng}, ${lat}`;
};
map.on('mousemove', this.mouseMoveHandler);
return this.container;
}
onRemove() {
this.map.off('mousemove', this.mouseMoveHandler);
this.container.remove();
this.map = null;
}
}
Ключевой момент: обязательное освобождение обработчиков в
onRemove, иначе возникает утечка памяти.
Контролы часто реализуют переключатели слоёв, режимов отображения или фильтров.
class StyleSwitcherControl {
onAdd(map) {
this.map = map;
this.container = document.createElement('div');
this.container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';
const streets = document.createElement('button');
streets.textContent = 'Streets';
const satellite = document.createElement('button');
satellite.textContent = 'Satellite';
streets.oncl ick = () => {
this.map.setStyle('mapbox://styles/mapbox/streets-v11');
};
satellite.oncl ick = () => {
this.map.setStyle('mapbox://styles/mapbox/satellite-v9');
};
this.container.appendChild(streets);
this.container.appendChild(satellite);
return this.container;
}
onRemove() {
this.container.remove();
this.map = null;
}
}
При смене стиля важно учитывать, что все кастомные слои и источники будут пересозданы.
Контролы могут выполнять асинхронные операции: загрузку данных, API-запросы, геокодирование.
class SearchControl {
onAdd(map) {
this.map = map;
this.container = document.createElement('div');
this.container.className = 'mapboxgl-ctrl';
const input = document.createElement('input');
input.placeholder = 'Поиск...';
const results = document.createElement('div');
input.onin put = async () => {
const query = input.value;
if (query.length < 3) return;
const res = await fetch(`/api/search?q=${query}`);
const data = await res.json();
results.innerHTML = '';
data.features.forEach(f => {
const item = document.createElement('div');
item.textContent = f.name;
item.oncl ick = () => {
this.map.flyTo({
center: f.center,
zoom: 12
});
};
results.appendChild(item);
});
};
this.container.appendChild(input);
this.container.appendChild(results);
return this.container;
}
onRemove() {
this.container.remove();
this.map = null;
}
}
Контролы распределяются по углам карты через второй аргумент
addControl. Важно учитывать:
map.addControl(new MyControl(), 'bottom-left');
Контролы часто управляют слоями карты:
class LayerToggleControl {
onAdd(map) {
this.map = map;
this.container = document.createElement('div');
this.container.className = 'mapboxgl-ctrl';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = true;
const label = document.createElement('label');
label.textContent = 'Показывать POI';
checkbox.oncha nge = () => {
this.map.setLayoutProperty(
'poi-layer',
'visibility',
checkbox.checked ? 'visible' : 'none'
);
};
this.container.appendChild(checkbox);
this.container.appendChild(label);
return this.container;
}
onRemove() {
this.container.remove();
this.map = null;
}
}
Кастомные контролы становятся частью UI-архитектуры карты, поэтому важны структурные принципы:
Mapbox GL JS не ограничивает стилизацию контролов, но базовые классы должны сохраняться.
.custom-control button {
background: #1e1e1e;
color: white;
border: none;
padding: 6px 10px;
cursor: pointer;
}
.custom-control button:hover {
background: #333;
}
Контейнер:
this.container.className = 'mapboxgl-ctrl custom-control';
mapboxgl-ctrl-grouponRemoveКонтролы могут быть объединены логически через общий контейнер или независимую регистрацию:
map.addControl(new ZoomControl(), 'top-right');
map.addControl(new FilterControl(), 'top-right');
map.addControl(new LegendControl(), 'bottom-right');
Mapbox автоматически группирует элементы, сохраняя порядок добавления.