В Google Maps JavaScript API оверлеями называются объекты,
отображаемые поверх карты и связанные с её системой координат. К ним
относятся как встроенные элементы (маркеры, полигоны, полилинии), так и
пользовательские оверлеи, создаваемые через наследование от класса
OverlayView.
Для управления пользовательскими оверлеями API предоставляет набор специальных методов жизненного цикла. Эти методы автоматически вызываются картографическим движком при добавлении, обновлении и удалении объекта с карты.
Основные методы жизненного цикла:
onAdd()draw()onRemove()Дополнительно важную роль играют методы:
setMap()getMap()getPanes()getProjection()Жизненный цикл пользовательского оверлея выглядит следующим образом:
Создание объекта
↓
setMap(map)
↓
onAdd()
↓
draw()
↓
draw() при изменениях карты
↓
setMap(null)
↓
onRemove()
Все пользовательские оверлеи наследуются от класса
google.maps.OverlayView.
Пример каркаса класса:
class CustomOverlay extends google.maps.OverlayView {
constructor(position) {
super();
this.position = position;
this.div = null;
}
onAdd() {
}
draw() {
}
onRemove() {
}
}
После создания объект привязывается к карте:
const overlay = new CustomOverlay({
lat: 55.751244,
lng: 37.618423
});
overlay.setMap(map);
В этот момент запускается жизненный цикл оверлея.
Метод onAdd() вызывается один раз после добавления
оверлея на карту.
Именно здесь обычно создаются DOM-элементы и выполняется их вставка в соответствующую панель карты.
onAdd() {
this.div = document.createElement("div");
this.div.style.position = "absolute";
this.div.innerHTML = "Мой оверлей";
const panes = this.getPanes();
panes.overlayLayer.appendChild(this.div);
}
Вызов происходит после выполнения:
overlay.setMap(map);
Однако вызов осуществляется не мгновенно из пользовательского кода, а внутренним механизмом API после регистрации объекта в карте.
Последовательность:
setMap(map)
↓
создание связей с картой
↓
onAdd()
↓
draw()
Внутри onAdd() доступен метод:
this.getPanes()
Он возвращает объект с контейнерами отображения.
Пример:
const panes = this.getPanes();
Доступные панели:
panes.mapPane
panes.overlayLayer
panes.overlayMouseTarget
panes.floatPane
panes.markerLayer
Каждая панель имеет собственный уровень отображения.
Чаще всего в onAdd() выполняются:
Пример:
onAdd() {
this.div = document.createElement("div");
this.div.className = "custom-label";
this.div.addEventListener("click", () => {
console.log("Клик по оверлею");
});
this.getPanes()
.overlayMouseTarget
.appendChild(this.div);
}
Метод draw() отвечает за позиционирование и обновление
отображения оверлея.
Он вызывается:
onAdd();Пример:
draw() {
const projection = this.getProjection();
const point = projection.fromLatLngToDivPixel(
new google.maps.LatLng(
this.position.lat,
this.position.lng
)
);
this.div.style.left = point.x + "px";
this.div.style.top = point.y + "px";
}
Ключевым элементом метода является объект проекции.
Получение:
const projection = this.getProjection();
Он позволяет преобразовывать координаты.
Например:
projection.fromLatLngToDivPixel()
или
projection.fromDivPixelToLatLng()
Координаты карты:
{
lat: 55.751244,
lng: 37.618423
}
преобразуются в экранные координаты:
const pixel = projection.fromLatLngToDivPixel(
new google.maps.LatLng(
55.751244,
37.618423
)
);
Результат:
{
x: 350,
y: 240
}
После этого можно позиционировать DOM-элемент:
this.div.style.left = `${pixel.x}px`;
this.div.style.top = `${pixel.y}px`;
Метод может вызываться десятки раз в секунду.
Например:
Пользователь тянет карту
↓
draw()
draw()
draw()
draw()
draw()
Поэтому внутри него не рекомендуется:
Неправильно:
draw() {
const div = document.createElement("div");
document.body.appendChild(div);
}
Правильно:
draw() {
const point = this.getProjection()
.fromLatLngToDivPixel(this.position);
this.div.style.left = point.x + "px";
this.div.style.top = point.y + "px";
}
Внутри draw() часто реализуется скрытие элементов,
выходящих за пределы области просмотра.
Пример:
draw() {
const projection = this.getProjection();
const point =
projection.fromLatLngToDivPixel(this.position);
if (!point) {
this.div.style.display = "none";
return;
}
this.div.style.display = "block";
this.div.style.left = point.x + "px";
this.div.style.top = point.y + "px";
}
Метод onRemove() вызывается при удалении оверлея с
карты.
Обычно используется для очистки ресурсов.
Пример:
onRemove() {
if (this.div) {
this.div.remove();
this.div = null;
}
}
Метод запускается после:
overlay.setMap(null);
или при уничтожении карты.
Схема:
setMap(null)
↓
onRemove()
↓
удаление DOM-узлов
В onRemove() рекомендуется:
Пример:
onRemove() {
if (this.clickHandler) {
this.div.removeEventListener(
"click",
this.clickHandler
);
this.clickHandler = null;
}
if (this.div) {
this.div.remove();
this.div = null;
}
}
Метод управляет привязкой оверлея к карте.
Добавление:
overlay.setMap(map);
Удаление:
overlay.setMap(null);
При добавлении:
setMap(map)
↓
onAdd()
↓
draw()
При удалении:
setMap(null)
↓
onRemove()
Таким образом, setMap() является точкой входа и выхода
жизненного цикла.
Метод возвращает текущую карту.
const map = overlay.getMap();
Проверка наличия карты:
if (overlay.getMap()) {
console.log("Оверлей активен");
}
После удаления:
overlay.setMap(null);
console.log(overlay.getMap());
Результат:
null
Рассмотрим полный пример.
class LabelOverlay extends google.maps.OverlayView {
constructor(position, text) {
super();
this.position = position;
this.text = text;
this.div = null;
}
onAdd() {
this.div = document.createElement("div");
this.div.className = "label";
this.div.innerText = this.text;
this.getPanes()
.overlayMouseTarget
.appendChild(this.div);
}
draw() {
const projection =
this.getProjection();
const pixel =
projection.fromLatLngToDivPixel(
this.position
);
this.div.style.left =
pixel.x + "px";
this.div.style.top =
pixel.y + "px";
}
onRemove() {
if (this.div) {
this.div.remove();
this.div = null;
}
}
}
Использование:
const label =
new LabelOverlay(
{ lat: 55.751244, lng: 37.618423 },
"Москва"
);
label.setMap(map);
Последовательность вызовов:
constructor()
↓
setMap(map)
↓
onAdd()
↓
draw()
↓
draw()
↓
draw()
↓
setMap(null)
↓
onRemove()
После удаления объект можно снова использовать.
overlay.setMap(null);
Позже:
overlay.setMap(map);
Жизненный цикл будет запущен заново:
onAdd()
↓
draw()
Поэтому код в onAdd() должен корректно создавать все
необходимые ресурсы каждый раз при повторном подключении.
Иногда оверлей загружает данные асинхронно.
Пример:
async onAdd() {
const response =
await fetch("/data.json");
const data =
await response.json();
this.div =
document.createElement("div");
this.div.textContent =
data.title;
this.getPanes()
.overlayLayer
.appendChild(this.div);
}
Следует учитывать возможность удаления объекта до завершения запроса.
Безопасный вариант:
async onAdd() {
const response =
await fetch("/data.json");
if (!this.getMap()) {
return;
}
const data =
await response.json();
this.div =
document.createElement("div");
this.div.textContent =
data.title;
this.getPanes()
.overlayLayer
.appendChild(this.div);
}
Неверно:
draw() {
const div =
document.createElement("div");
document.body.appendChild(div);
}
Каждый вызов создаёт новый элемент.
Правильно:
onAdd() {
this.div =
document.createElement("div");
}
Неверно:
onRemove() {
}
DOM-элементы остаются в памяти.
Правильно:
onRemove() {
this.div.remove();
this.div = null;
}
Неверно:
constructor() {
const projection =
this.getProjection();
}
Проекция ещё недоступна.
Правильно:
draw() {
const projection =
this.getProjection();
}
Неверно:
draw() {
for (let i = 0; i < 1000000; i++) {
}
}
Это ухудшает плавность карты.
Правильно:
draw() {
this.div.style.left =
this.pixelX + "px";
this.div.style.top =
this.pixelY + "px";
}
Метод onAdd()
Метод draw()
Метод onRemove()
Такое разделение обязанностей делает пользовательские оверлеи предсказуемыми, производительными и полностью совместимыми с внутренним механизмом рендеринга Google Maps JavaScript API.