DOM-элементы пользовательских контролов в Google Maps JavaScript API
представляют собой обычные HTML-узлы, которые добавляются в специальную
систему слоёв управления картой. В отличие от встроенных элементов
интерфейса, такие контролы полностью управляются разработчиком:
структура, стилизация, поведение и состояние определяются вручную, а
карта лишь предоставляет механизм их размещения через
map.controls.
Каждый контрол встраивается в одну из заранее определённых позиций
интерфейса карты. Эти позиции задаются перечислением
google.maps.ControlPosition и определяют, где именно будет
отображаться DOM-элемент:
TOP_LEFTTOP_CENTERTOP_RIGHTLEFT_TOPRIGHT_TOPLEFT_CENTERRIGHT_CENTERLEFT_BOTTOMRIGHT_BOTTOMBOTTOM_CENTERBOTTOM_LEFTBOTTOM_RIGHTКаждая позиция представляет собой массив DOM-элементов, который Google Maps самостоятельно размещает поверх карты.
Добавление контролов происходит через коллекцию:
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(controlDiv);
controlDiv — обычный HTML-элемент, который становится
частью интерфейса карты.
Пользовательский контрол обычно состоит из трёх уровней:
Пример минимальной структуры:
function createCustomControl(map) {
const controlDiv = document.createElement("div");
const controlUI = document.createElement("div");
const controlText = document.createElement("div");
controlUI.appendChild(controlText);
controlDiv.appendChild(controlUI);
map.controls[google.maps.ControlPosition.TOP_LEFT].push(controlDiv);
}
Без CSS такой контрол будет выглядеть как обычные вложенные блоки без оформления, поэтому стилизация является ключевым этапом.
Наиболее прямой способ управления внешним видом — установка стилей
через style:
controlDiv.style.margin = "10px";
controlDiv.style.backgroundColor = "#fff";
controlDiv.style.borderRadius = "8px";
controlDiv.style.boxShadow = "0 2px 6px rgba(0,0,0,0.3)";
controlDiv.style.cursor = "pointer";
controlDiv.style.userSelect = "none";
Такой подход гарантирует независимость от внешних CSS-файлов, но плохо масштабируется при усложнении интерфейса.
Более устойчивый подход — назначение классов:
controlDiv.className = "custom-map-control";
controlUI.className = "custom-map-control__ui";
controlText.className = "custom-map-control__text";
И соответствующий CSS:
.custom-map-control {
margin: 10px;
}
.custom-map-control__ui {
background: #ffffff;
border-radius: 10px;
padding: 8px 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
display: flex;
align-items: center;
}
.custom-map-control__text {
font-size: 14px;
font-family: Roboto, Arial, sans-serif;
color: #333;
}
Такой подход позволяет:
Контролы часто выступают как интерактивные элементы интерфейса. Для этого необходимо корректно обрабатывать события DOM.
controlUI.addEventListener("click", () => {
map.setZoom(map.getZoom() + 1);
});
Поскольку контрол находится поверх карты, любые события могут «проваливаться» в слой карты (drag, zoom, click). Чтобы этого избежать:
controlUI.addEventListener("mousedown", (e) => {
e.stopPropagation();
});
controlUI.addEventListener("click", (e) => {
e.stopPropagation();
});
Это критично для корректного поведения сложных интерфейсов (поисковые поля, формы, фильтры).
Интерактивность обычно реализуется через CSS:
.custom-map-control__ui:hover {
background: #f5f5f5;
}
.custom-map-control__ui:active {
transform: scale(0.98);
}
Также можно управлять состояниями через классы:
controlUI.addEventListener("mouseenter", () => {
controlUI.classList.add("is-hovered");
});
controlUI.addEventListener("mouseleave", () => {
controlUI.classList.remove("is-hovered");
});
Контролы не адаптируются автоматически под тему карты. Поэтому требуется ручная синхронизация.
.custom-map-control--dark .custom-map-control__ui {
background: #2b2b2b;
color: #ffffff;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}
Переключение темы:
function setDarkMode(enabled) {
if (enabled) {
controlDiv.classList.add("custom-map-control--dark");
} else {
controlDiv.classList.remove("custom-map-control--dark");
}
}
Часто контролы отражают состояние карты: масштаб, центр, активные слои.
map.addListener("zoom_changed", () => {
controlText.textContent = `Zoom: ${map.getZoom()}`;
});
map.addListener("center_changed", () => {
const center = map.getCenter();
controlText.textContent =
`${center.lat().toFixed(4)}, ${center.lng().toFixed(4)}`;
});
Контролы могут включать сложные интерфейсы: формы поиска, фильтры, списки.
const input = document.createElement("input");
input.type = "text";
input.placeholder = "Поиск";
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ address: input.value }, (results, status) => {
if (status === "OK") {
map.setCenter(results[0].geometry.location);
}
});
}
});
controlUI.appendChild(input);
Стилизация input требует отдельного внимания:
.custom-map-control input {
border: none;
outline: none;
font-size: 14px;
background: transparent;
width: 180px;
}
Контролы не адаптируются автоматически под экран, поэтому необходимо учитывать:
.custom-map-control {
max-width: 90vw;
}
@media (max-width: 600px) {
.custom-map-control__ui {
padding: 6px 10px;
font-size: 13px;
}
}
Порядок отображения контролов внутри одной позиции определяется порядком добавления:
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(controlA);
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(controlB);
controlB окажется ниже controlA.
Для сложных интерфейсов важно избегать перекрытий и учитывать z-index:
.custom-map-control {
position: relative;
z-index: 5;
}
Для предотвращения конфликтов CSS можно использовать Shadow DOM:
const shadow = controlDiv.attachShadow({ mode: "open" });
shadow.innerHTML = `
<style>
.btn {
background: #fff;
padding: 8px 12px;
border-radius: 8px;
cursor: pointer;
}
</style>
<div class="btn">Контрол</div>
`;
Преимущества:
При использовании современных фреймворков контролы часто генерируются как отдельные компоненты, которые затем монтируются в DOM-контейнер карты.
Логика остаётся прежней: результатом является HTML-элемент, который добавляется в:
map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(element);
Часто возникающие проблемы:
stopPropagation, из-за чего карта
перетаскивается при работе с контроломКонтролы не зависят от масштаба карты, но визуально могут конфликтовать с плотностью данных. Поэтому часто применяются полупрозрачные фоны:
background: rgba(255, 255, 255, 0.9);
или динамическая адаптация:
map.addListener("zoom_changed", () => {
const zoom = map.getZoom();
if (zoom > 12) {
controlDiv.style.opacity = "1";
} else {
controlDiv.style.opacity = "0.85";
}
});
function createStyledControl(map) {
const controlDiv = document.createElement("div");
controlDiv.className = "custom-map-control";
const button = document.createElement("button");
button.className = "custom-map-control__button";
button.textContent = "Центрировать";
button.addEventListener("click", (e) => {
e.stopPropagation();
map.setCenter({ lat: 0, lng: 0 });
});
controlDiv.appendChild(button);
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(controlDiv);
}
.custom-map-control__button {
padding: 8px 14px;
border-radius: 8px;
border: none;
background: #4285f4;
color: #fff;
cursor: pointer;
}
Система пользовательских контролов в Google Maps JavaScript API опирается на прямое управление DOM-структурой и требует строгого разделения логики, поведения и визуального слоя.