Autocomplete в интерфейсах геопоиска представляет собой механизм предсказания и подстановки результатов по мере ввода текста. В контексте MapLibre GL JS он чаще всего используется совместно с геокодерами и собственными UI-контролами, обеспечивая быстрый выбор географических объектов без необходимости полного ввода запроса.
Ключевая задача autocomplete в картографических приложениях — минимизировать количество запросов к серверу и одновременно повысить точность ввода за счёт подсказок, основанных на частичном совпадении строк, локальных индексах или внешних API.
Типовая архитектура автодополнения в связке с MapLibre GL JS включает несколько слоёв:
Связь между слоями строится асинхронно: пользователь вводит текст → выполняется debounce → отправляется запрос → возвращается список → обновляется UI → выбранный результат синхронизируется с картой.
Основой autocomplete является контрол ввода. В MapLibre GL JS чаще
всего создаётся пользовательский control через
map.addControl.
class SearchControl {
onAdd(map) {
this.map = map;
this.container = document.createElement('div');
this.container.className = 'maplibre-search';
this.input = document.createElement('input');
this.input.type = 'text';
this.input.placeholder = 'Поиск...';
this.list = document.createElement('div');
this.list.className = 'search-suggestions';
this.container.appendChild(this.input);
this.container.appendChild(this.list);
this.bindEvents();
return this.container;
}
onRemove() {
this.container.parentNode.removeChild(this.container);
this.map = undefined;
}
bindEvents() {
this.input.addEventListener('input', (e) => {
this.onInput(e.target.value);
});
}
onInput(value) {
// будет реализовано далее
}
}
Такой подход позволяет встроить поиск как полноценный элемент управления картой.
Без ограничения частоты запросов autocomplete быстро перегружает API. Решение — debounce.
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
Применение в контроле:
this.onIn put = debounce((value) => {
if (value.length < 3) {
this.clearSuggestions();
return;
}
this.fetchSuggestions(value);
}, 300);
Оптимальная задержка обычно находится в диапазоне 200–400 мс, в зависимости от скорости API и требований UX.
Autocomplete почти всегда опирается на геокодинг. Пример запроса к Nominatim:
async fetchSuggestions(query) {
const url = `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}`;
const response = await fetch(url);
const data = await response.json();
this.renderSuggestions(data);
}
Каждый элемент ответа содержит координаты, название и тип объекта, что позволяет использовать его напрямую в MapLibre GL JS.
UI списка должен быть быстрым и минимально инвазивным.
renderSuggestions(items) {
this.list.innerHTML = '';
items.forEach((item, index) => {
const el = document.createElement('div');
el.className = 'suggestion-item';
el.textContent = item.display_name;
el.addEventListener('click', () => {
this.selectSuggestion(item);
});
this.list.appendChild(el);
});
}
Важно избегать сложной DOM-структуры: autocomplete должен оставаться лёгким даже при десятках обновлений в секунду.
После выбора результата происходит синхронизация с картой.
selectSuggestion(item) {
const lng = parseFloat(item.lon);
const lat = parseFloat(item.lat);
this.map.flyTo({
center: [lng, lat],
zoom: 14
});
new maplibregl.Marker()
.setLngLat([lng, lat])
.addTo(this.map);
this.clearSuggestions();
this.input.value = item.display_name;
}
Использование flyTo создаёт плавный переход, а маркер
фиксирует выбранную точку.
Autocomplete без клавиатуры считается неполноценным. Основные сценарии:
bindEvents() {
this.input.addEventListener('input', (e) => {
this.onInput(e.target.value);
});
this.input.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown') this.moveDown();
if (e.key === 'ArrowUp') this.moveUp();
if (e.key === 'Enter') this.selectActive();
if (e.key === 'Escape') this.clearSuggestions();
});
}
Поддержка активного индекса:
this.activeIndex = -1;
Список autocomplete требует управления состоянием:
Пример кеширования:
this.cache = new Map();
async fetchSuggestions(query) {
if (this.cache.has(query)) {
this.renderSuggestions(this.cache.get(query));
return;
}
const response = await fetch(...);
const data = await response.json();
this.cache.set(query, data);
this.renderSuggestions(data);
}
Кеширование особенно эффективно при повторяющихся запросах или медленном соединении.
Геокодинг может возвращать:
Рекомендуемая стратегия — мягкая деградация:
async fetchSuggestions(query) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network error');
const data = await response.json();
this.renderSuggestions(data);
} catch (e) {
this.renderSuggestions([]);
}
}
UI должен оставаться стабильным даже при отсутствии данных.
При высокочастотном вводе критичны следующие оптимизации:
AbortControllerПример отмены запросов:
this.controller = new AbortController();
fetch(url, { signal: this.controller.signal });
При новом запросе предыдущий отменяется:
if (this.controller) this.controller.abort();
this.controller = new AbortController();
В картографических интерфейсах применяются специфические паттерны:
Эти правила позволяют избежать визуального шума и улучшить предсказуемость поведения интерфейса.
CSS играет критическую роль в восприятии autocomplete:
.maplibre-search {
position: absolute;
top: 10px;
left: 10px;
width: 300px;
background: white;
}
.search-suggestions {
max-height: 240px;
overflow-y: auto;
}
.suggestion-item {
padding: 8px;
cursor: pointer;
}
Важным аспектом является абсолютное позиционирование относительно карты и предотвращение перекрытия ключевых элементов управления.
MapLibre GL JS не ограничивает выбор backend-решения. Часто используются:
Абстракция слоя запроса позволяет переключать backend без изменения UI-логики:
class Geocoder {
async search(query) {
return fetch(`/api/geocode?q=${query}`).then(r => r.json());
}
}
Такой подход упрощает масштабирование и локализацию поиска.
Autocomplete может учитывать текущий viewport карты:
Пример передачи bbox:
const bbox = this.map.getBounds().toArray().flat();
const url = `/search?q=${query}&bbox=${bbox.join(',')}`;
Это значительно повышает релевантность результатов в локальных сценариях использования карты.