Анимация маркеров

Анимация маркеров в Google Maps JavaScript API применяется для привлечения внимания к объектам на карте, визуального отображения изменений состояния, индикации новых событий и повышения удобства взаимодействия с картографическим интерфейсом.

Анимированные маркеры позволяют:

  • выделять важные точки;
  • показывать момент появления объекта;
  • обозначать активный или выбранный элемент;
  • визуализировать перемещение объектов;
  • создавать более интерактивный пользовательский интерфейс.

Google Maps JavaScript API предоставляет встроенные механизмы анимации, а также позволяет реализовывать собственные анимационные эффекты средствами JavaScript и CSS.


Встроенные типы анимации

API содержит перечисление google.maps.Animation, которое предоставляет два стандартных эффекта:

Анимация Описание
DROP Плавное падение маркера сверху карты
BOUNCE Подпрыгивание маркера на месте

Эти эффекты работают только с объектами класса google.maps.Marker.


Анимация DROP

Эффект DROP создаёт впечатление, что маркер падает на карту сверху.

Создание маркера с анимацией

const marker = new google.maps.Marker({
    position: {
        lat: 55.7558,
        lng: 37.6176
    },
    map: map,
    animation: google.maps.Animation.DROP
});

После создания маркер плавно появится на карте.

Особенности DROP

  • выполняется один раз;
  • запускается автоматически при создании маркера;
  • не повторяется циклически;
  • подходит для отображения новых объектов.

Запуск DROP после создания маркера

Анимацию можно включить позже.

const marker = new google.maps.Marker({
    position: {
        lat: 55.7558,
        lng: 37.6176
    },
    map: map
});

marker.setAnimation(
    google.maps.Animation.DROP
);

Такой подход удобен при динамическом добавлении точек после загрузки карты.


Анимация BOUNCE

Эффект BOUNCE заставляет маркер непрерывно подпрыгивать.

Создание маркера

const marker = new google.maps.Marker({
    position: {
        lat: 55.7558,
        lng: 37.6176
    },
    map: map,
    animation: google.maps.Animation.BOUNCE
});

После отображения маркер будет постоянно двигаться вверх и вниз.


Остановка анимации

Для отключения анимации используется значение null.

marker.setAnimation(null);

Полный пример:

const marker = new google.maps.Marker({
    position: {
        lat: 55.7558,
        lng: 37.6176
    },
    map: map,
    animation: google.maps.Animation.BOUNCE
});

setTimeout(() => {
    marker.setAnimation(null);
}, 3000);

Через три секунды подпрыгивание прекратится.


Переключение анимации по клику

Часто используется сценарий, при котором пользователь нажимает на маркер для его выделения.

marker.addListener("click", () => {

    if (marker.getAnimation() !== null) {
        marker.setAnimation(null);
    } else {
        marker.setAnimation(
            google.maps.Animation.BOUNCE
        );
    }

});

После каждого нажатия состояние анимации меняется.


Выделение выбранного объекта

При работе со списками объектов полезно анимировать только активный маркер.

Пример

let activeMarker = null;

function selectMarker(marker) {

    if (activeMarker) {
        activeMarker.setAnimation(null);
    }

    marker.setAnimation(
        google.maps.Animation.BOUNCE
    );

    activeMarker = marker;
}

Использование:

markers.forEach(marker => {

    marker.addListener("click", () => {
        selectMarker(marker);
    });

});

В каждый момент времени анимируется только один объект.


Временная анимация

Постоянное подпрыгивание может отвлекать пользователя. Намного удобнее запускать эффект на короткое время.

marker.addListener("click", () => {

    marker.setAnimation(
        google.maps.Animation.BOUNCE
    );

    setTimeout(() => {
        marker.setAnimation(null);
    }, 1500);

});

Такой подход используется чаще всего.


Анимация при загрузке данных

При получении объектов с сервера можно визуально показать их появление.

fetch("/api/places")
    .then(response => response.json())
    .then(data => {

        data.forEach(place => {

            new google.maps.Marker({
                position: place.position,
                map: map,
                animation:
                    google.maps.Animation.DROP
            });

        });

    });

Каждая новая точка будет плавно появляться на карте.


Последовательное появление маркеров

Иногда необходимо создать эффект постепенного заполнения карты.

locations.forEach((location, index) => {

    setTimeout(() => {

        new google.maps.Marker({
            position: location,
            map: map,
            animation:
                google.maps.Animation.DROP
        });

    }, index * 300);

});

Каждый следующий маркер появляется через 300 миллисекунд после предыдущего.


Каскадная анимация большого количества объектов

Для сотен маркеров одновременное использование анимации может негативно влиять на производительность.

Лучше применять интервалы появления:

for (let i = 0; i < locations.length; i++) {

    setTimeout(() => {

        createMarker(locations[i]);

    }, i * 100);

}

Так уменьшается нагрузка на браузер и повышается плавность интерфейса.


Изменение позиции маркера с анимацией

Стандартный API не содержит встроенной анимации перемещения. Для плавного движения необходимо изменять координаты вручную.

Исходные координаты

const start = {
    lat: 55.7558,
    lng: 37.6176
};

const end = {
    lat: 55.7658,
    lng: 37.6276
};

Создание маркера

const marker = new google.maps.Marker({
    position: start,
    map: map
});

Плавное перемещение

function animateMarker(marker, start, end) {

    const steps = 100;

    let currentStep = 0;

    const interval = setInterval(() => {

        currentStep++;

        const lat =
            start.lat +
            (end.lat - start.lat) *
            currentStep / steps;

        const lng =
            start.lng +
            (end.lng - start.lng) *
            currentStep / steps;

        marker.setPosition({
            lat,
            lng
        });

        if (currentStep >= steps) {
            clearInterval(interval);
        }

    }, 20);

}

Запуск:

animateMarker(marker, start, end);

Получается плавное движение между двумя точками.


Использование requestAnimationFrame

Для более качественной анимации рекомендуется применять requestAnimationFrame.

function moveMarker(marker, start, end, duration) {

    const startTime = performance.now();

    function animate(currentTime) {

        const elapsed =
            currentTime - startTime;

        const progress =
            Math.min(elapsed / duration, 1);

        const lat =
            start.lat +
            (end.lat - start.lat) *
            progress;

        const lng =
            start.lng +
            (end.lng - start.lng) *
            progress;

        marker.setPosition({
            lat,
            lng
        });

        if (progress < 1) {
            requestAnimationFrame(
                animate
            );
        }

    }

    requestAnimationFrame(animate);

}

Преимущества:

  • более плавное движение;
  • синхронизация с частотой обновления экрана;
  • меньшая нагрузка на процессор;
  • отсутствие лишних вызовов таймеров.

Имитация движения транспорта

Частая задача — отображение такси, автобусов, курьеров и других подвижных объектов.

setInterval(async () => {

    const response =
        await fetch("/vehicle");

    const position =
        await response.json();

    marker.setPosition(position);

}, 5000);

Однако резкое изменение координат выглядит неестественно.

Лучше использовать промежуточную анимацию:

animateMarker(
    marker,
    marker.getPosition().toJSON(),
    position
);

Тогда объект будет двигаться плавно.


Анимация по маршруту

Если имеется массив координат, маркер можно перемещать последовательно между ними.

const route = [
    { lat: 55.75, lng: 37.61 },
    { lat: 55.76, lng: 37.62 },
    { lat: 55.77, lng: 37.63 }
];

Функция движения:

let index = 0;

function moveNext() {

    if (index >= route.length - 1) {
        return;
    }

    moveMarker(
        marker,
        route[index],
        route[index + 1],
        1000
    );

    index++;

    setTimeout(moveNext, 1000);

}

Запуск:

moveNext();

Маркер будет проходить по всем точкам маршрута.


Анимация изменения иконки

Иногда достаточно визуально изменить внешний вид маркера.

Обычная иконка

marker.setIcon("default.png");

Активная иконка

marker.setIcon("active.png");

Пример:

marker.addListener("click", () => {

    marker.setIcon(
        "active.png"
    );

});

Подобный приём часто заменяет полноценную анимацию.


CSS-анимация пользовательских маркеров

Для современных карт рекомендуется использовать AdvancedMarkerElement.

Он позволяет вставлять HTML-разметку внутрь маркера.

Создание элемента

const markerContent =
    document.createElement("div");

markerContent.className =
    "custom-marker";

const marker =
    new google.maps.marker.AdvancedMarkerElement({
        map,
        position: {
            lat: 55.7558,
            lng: 37.6176
        },
        content: markerContent
    });

CSS-эффект пульсации

.custom-marker {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: red;

    animation:
        pulse 1.5s infinite;
}

@keyframes pulse {

    0% {
        transform: scale(1);
    }

    50% {
        transform: scale(1.5);
    }

    100% {
        transform: scale(1);
    }

}

Получается эффект мигающей точки, характерный для геолокации и систем мониторинга.


Эффект вращения

.custom-marker {

    animation:
        rotateMarker 2s linear infinite;

}

@keyframes rotateMarker {

    from {
        transform: rotate(0deg);
    }

    to {
        transform: rotate(360deg);
    }

}

Подходит для индикаторов загрузки и специальных обозначений.


Комбинирование эффектов

Можно объединять несколько CSS-анимаций.

.custom-marker {

    animation:
        pulse 2s infinite,
        rotateMarker 4s linear infinite;

}

Одновременно выполняются увеличение и вращение.


Управление анимацией через классы

Более гибкий подход — переключение CSS-классов.

.marker-active {
    animation:
        pulse 1s infinite;
}

Добавление:

markerContent.classList.add(
    "marker-active"
);

Удаление:

markerContent.classList.remove(
    "marker-active"
);

Такой способ особенно удобен для сложных интерфейсов.


Производительность анимации

При работе с большим количеством объектов необходимо учитывать ограничения браузера.

Основные рекомендации:

  • избегать постоянной анимации сотен маркеров;
  • использовать CSS-анимации вместо частых JavaScript-таймеров;
  • применять requestAnimationFrame для движения объектов;
  • отключать анимацию вне видимой области карты;
  • анимировать только активные элементы;
  • использовать кластеризацию маркеров для крупных наборов данных.

Нагрузка особенно заметна при одновременной анимации нескольких сотен объектов, поэтому визуальные эффекты должны применяться избирательно и только там, где они действительно несут информационную ценность.