Позиционирование всплывающих окон в Google Maps JavaScript API
опирается на две системы координат: географическую (широта и долгота) и
экранную (пиксели относительно контейнера карты). Любое визуальное окно,
связанное с точкой на карте, в конечном итоге преобразуется из
LatLng в координаты DOM-слоя карты.
Ключевым элементом этой системы является проекция карты,
предоставляемая через интерфейс Projection. Она отвечает за
преобразование географических координат в пиксельные значения
относительно текущего состояния карты (центр, масштаб, поворот).
InfoWindow является стандартным механизмом отображения
всплывающих окон. Его позиционирование напрямую связано с объектом
LatLng, к которому оно привязывается.
const map = new google.maps.Map(document.getElementById("map"), {
center: { lat: 40.73061, lng: -73.935242 },
zoom: 10
});
const marker = new google.maps.Marker({
position: { lat: 40.73061, lng: -73.935242 },
map: map
});
const infoWindow = new google.maps.InfoWindow({
content: "<div>Точка интереса</div>"
});
marker.addListener("click", () => {
infoWindow.open({
anchor: marker,
map: map
});
});
В этом случае окно автоматически рассчитывает своё положение
относительно якоря anchor. Внутренне используется привязка
к пиксельной позиции маркера, а также учитываются размеры окна и
смещение для предотвращения выхода за границы карты.
Позиционирование окон через anchor предполагает наличие
точки привязки. У маркеров эта точка определяется свойством
anchorPoint, которое задаёт место, относительно которого
рассчитывается всплывающее окно.
Для более точной настройки используется pixelOffset,
позволяющий смещать окно относительно базовой точки.
const infoWindow = new google.maps.InfoWindow({
content: "Смещение окна",
pixelOffset: new google.maps.Size(0, -30)
});
pixelOffset принимает объект
Size(width, height), где:
width смещает окно по горизонталиheight смещает окно по вертикалиОтрицательное значение по вертикали часто используется для подъёма окна над маркером.
Для ручного позиционирования используется слой проекции карты. Он
доступен через OverlayView.
class CustomOverlay extends google.maps.OverlayView {
onAdd() {
this.div = document.createElement("div");
this.div.style.position = "absolute";
this.getPanes().overlayMouseTarget.appendChild(this.div);
}
draw() {
const projection = this.getProjection();
const position = projection.fromLatLngToDivPixel(this.latlng);
this.div.style.left = position.x + "px";
this.div.style.top = position.y + "px";
}
onRemove() {
this.div.parentNode.removeChild(this.div);
}
}
Методы преобразования:
fromLatLngToDivPixel(latLng) — перевод в пикселиfromDivPixelToLatLng(pixel) — обратное
преобразованиеЭти функции учитывают текущий zoom и центр карты, что позволяет синхронизировать DOM-элементы с картой.
OverlayView предоставляет полный контроль над
размещением HTML-элементов поверх карты. Это используется для реализации
кастомных окон, тултипов и интерактивных попапов.
Жизненный цикл:
onAdd() — добавление DOM-элементаdraw() — вычисление позицииonRemove() — очистка ресурсовКлючевой момент заключается в том, что draw() вызывается
при каждом изменении состояния карты: zoom, pan, resize.
При открытии окна часто требуется изменить центр карты так, чтобы окно полностью попадало в видимую область. Для этого используются методы:
panTo(latLng)panBy(x, y)fitBounds(bounds)map.panTo(marker.getPosition());
При сложных интерфейсах учитывается высота окна, чтобы избежать его обрезания верхней границей viewport.
При изменении zoom пиксельные координаты пересчитываются
автоматически, однако пользовательские окна требуют явного обновления
позиции. В OverlayView это решается повторным вычислением в
draw().
Особое внимание уделяется:
При позиционировании окон рядом с границами карты возникает проблема выхода за пределы viewport. Встроенные окна автоматически применяют логики “clamping” и смещения.
В кастомных реализациях требуется ручная проверка:
Пример корректировки:
if (position.y < 50) {
this.div.style.top = "50px";
}
Каждое окно в Google Maps размещается в одном из панов
(panes):
overlayLayer — базовые оверлеиoverlayMouseTarget — интерактивные элементыfloatPane — всплывающие окна и InfoWindowВыбор слоя определяет z-index и поведение при наведении мыши.
this.getPanes().floatPane.appendChild(this.div);
Неправильный выбор слоя приводит к проблемам перекрытия и невозможности взаимодействия.
Позиционирование окон зависит от событий:
bounds_changedzoom_changedcenter_changedresizeДля кастомных окон требуется привязка обновления позиции к этим событиям:
google.maps.event.addListener(map, "zoom_changed", () => {
overlay.draw();
});
При одновременном отображении нескольких окон важно учитывать:
Часто применяется стратегия “активного окна”, при которой только одно окно имеет приоритетную позицию, остальные смещаются или скрываются.
Маркер в Google Maps имеет фиксированную точку привязки (обычно нижний центр). Это влияет на позиционирование окна:
const infoWindow = new google.maps.InfoWindow({
pixelOffset: new google.maps.Size(0, -40)
});
При изменении размеров контейнера карты необходимо пересчитывать позицию всех кастомных окон. Это связано с тем, что пиксельные координаты зависят от размеров viewport.
Типичный механизм:
resize на контейнереgoogle.maps.event.trigger(map, "resize")При резком изменении zoom положение окна может визуально “прыгать”. Это связано с округлением пиксельных значений и изменением масштаба тайлов.
Для стабилизации применяется:
Все кастомные окна в конечном счете используют
position: absolute внутри слоев карты. Это требует строгого
контроля:
.custom-popup {
position: absolute;
transform: translate(-50%, -100%);
}
Такой подход позволяет центрировать окно относительно точки привязки без дополнительных вычислений.
При работе с координатами, близкими к ±180° долготы, возникает эффект “перепрыгивания” позиции. Это связано с циклической природой карты мира.
Решение заключается в:
wrapLongitudeПозиционирование окон в Google Maps JavaScript API строится на сочетании:
LatLng)Projection)Корректная работа достигается только при синхронном учете всех уровней трансформации: от координат мира до абсолютного положения элемента в DOM.