Анимация маркеров | MapGL | 2GIS Documentation
MapGL JS API

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

Вы можете добавлять анимацию к маркерам на карте:

Также вы можете анимировать HTML-маркер при помощи CSS или Lottie.

Чтобы добавить анимацию к маркеру, добавьте объект Marker на карту и укажите координаты центра маркера:

const marker = new mapgl.Marker(map, {
    coordinates: [55.31878, 25.23584],
});

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

Чтобы добавить анимацию движения по окружности, используйте функцию animateCircle:

function animateCircle(marker, centerCoords, radius, duration) {
    const startTime = performance.now();

    function frame(time) {
        const elapsed = (time - startTime) % duration; // Время с начала текущей итерации анимации
        const angle = (2 * Math.PI * elapsed) / duration; // Текущий угол в радианах

        const newCoords = [
            centerCoords[0] + radius * Math.cos(angle), // Вычисление долготы
            centerCoords[1] + radius * Math.sin(angle), // Вычисление широты
        ];

        marker.setCoordinates(newCoords); // Установка новых координат маркера

        requestAnimationFrame(frame); // Запуск следующего кадра анимации
    }

    requestAnimationFrame(frame);
}

// Вызов функции анимации
animateCircle(marker, [55.31878, 25.23584], 0.01, 5000);

Укажите следующие параметры функции animateCircle:

  • marker: объект маркера, для которого будет добавлена анимация.
  • centerCoords: координаты центра окружности в формате [долгота, широта].
  • radius: радиус окружности в географических координатах (в градусах).
  • duration: длительность одного полного оборота в миллисекундах.
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>2GIS Map API</title>
        <style>
            html,
            body,
            #container {
                margin: 0;
                width: 100%;
                height: 100%;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
<div id="container"></div>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
        const map = new mapgl.Map('container', {
            center: [55.323, 25.235],
            zoom: 10.5,
            key: 'Your API access key',
        });

        const marker = new mapgl.Marker(map, {
            coordinates: [55.323, 25.235],
        });

        const centerCoords = [55.323, 25.235];
        const radius = 0.1;
        const duration = 5000;

        function animateMarkerInCircle(marker, centerCoords, radius, duration) {
            const startTime = performance.now();

            function frame(time) {
                const elapsed = (time - startTime) % duration;
                const angle = (2 * Math.PI * elapsed) / duration;
                const newCoords = [
                    centerCoords[0] + radius * Math.cos(angle),
                    centerCoords[1] + radius * Math.sin(angle),
                ];

                marker.setCoordinates(newCoords);
                requestAnimationFrame(frame);
            }

            requestAnimationFrame(frame);
        }

        animateMarkerInCircle(marker, centerCoords, radius, duration);
</script>
</body>
</html>

При анимации в виде прыжка маркер двигается вверх и вниз, создавая эффект подпрыгивания.

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

Чтобы добавить анимацию в виде прыжка, используйте функцию animateJump:

function animateJump(marker, map, baseCoords, amplitude, duration) {
    const startTime = performance.now();

    function frame(time) {
        const elapsed = (time - startTime) % duration; // Время с начала текущей итерации анимации
        const bounce = Math.sin((2 * Math.PI * elapsed) / duration) * amplitude; // Высота прыжка

        // Преобразование географических координат в пиксельные
        const basePixelCoords = map.project(baseCoords);

        // Добавление смещения по оси Y (по вертикали) в пикселях
        const newPixelCoords = [
            basePixelCoords[0], // X остаётся без изменений
            basePixelCoords[1] - bounce, // Уменьшение Y для прыжка вверх
        ];

        // Преобразование пиксельных координат обратно в географические
        const newGeoCoords = map.unproject(newPixelCoords);

        // Установка новых координат маркера
        marker.setCoordinates(newGeoCoords);

        requestAnimationFrame(frame); // Запуск следующего кадра анимации
    }

    requestAnimationFrame(frame);
}

// Вызов функции анимации
animateJump(marker, map, [55.31878, 25.23584], 20, 1000);

Укажите следующие параметры функции animateJump:

  • marker: объект маркера, для которого будет добавлена анимация.
  • map: объект карты, используемый для преобразования координат.
  • baseCoords: исходные координаты маркера в формате [долгота, широта].
  • amplitude: амплитуда прыжка (высота движения) в пикселях.
  • duration: длительность одного полного прыжка (вверх и вниз) в миллисекундах.
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>2GIS Map API</title>
        <style>
            html,
            body,
            #container {
                margin: 0;
                width: 100%;
                height: 100%;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <div id="container"></div>
        <script src="https://mapgl.2gis.com/api/js/v1"></script>
        <script>
        const map = new mapgl.Map('container', {
            center: [55.323, 25.235],
            zoom: 10.5,
            key: 'Your API access key',
        });

        const marker = new mapgl.Marker(map, {
            coordinates: [55.323, 25.235],
        });

        const centerCoords = [55.323, 25.235];
        const radius = 0.1;
        const duration = 5000;

        function jumpMarkerAnimation(marker, map, baseCoords, amplitude, duration) {
            const startTime = performance.now();

            function frame(time) {
                const elapsed = (time - startTime) % duration;
                const bounce = Math.sin((2 * Math.PI * elapsed) / duration) * amplitude;

                const basePixelCoords = map.project(baseCoords);
                const newPixelCoords = [
                    basePixelCoords[0],
                    basePixelCoords[1] - bounce,
                ];
                const newGeoCoords = map.unproject(newPixelCoords);
                marker.setCoordinates(newGeoCoords);

                requestAnimationFrame(frame);
            }

            requestAnimationFrame(frame);
        }

        jumpMarkerAnimation(marker, map, [55.323, 25.235], 20, 1000);

    </script>
    </body>
</html>

Анимация движения по маршруту выполняется с помощью изменения географических координат маркера.

Чтобы добавить анимацию движения по координатам, используйте функцию animateTravel:

// Функция для интерполяции между двумя точками
function interpolateCoordinates(coord1, coord2, t) {
    return [coord1[0] + (coord2[0] - coord1[0]) * t, coord1[1] + (coord2[1] - coord1[1]) * t];
}

function animateTravel(marker, route, durationPerSegment) {
    let segmentIndex = 0;

    function animateSegment(startTime) {
        const elapsedTime = performance.now() - startTime;
        const t = elapsedTime / durationPerSegment; // Процент завершения сегмента

        if (t < 1) {
            // Интерполяция координат
            const newCoords = interpolateCoordinates(
                route[segmentIndex],
                route[segmentIndex + 1],
                t,
            );
            marker.setCoordinates(newCoords);

            // Продолжение анимации текущего сегмента
            requestAnimationFrame(() => animateSegment(startTime));
        } else {
            // Переход к следующему сегменту
            segmentIndex++;
            if (segmentIndex < route.length - 1) {
                animateSegment(performance.now());
            } else {
                // Зацикливание маршрута
                segmentIndex = 0;
                animateSegment(performance.now());
            }
        }
    }

    // Начало анимации первого сегмента
    if (route.length > 1) {
        animateSegment(performance.now());
    }
}

// Вызов функции анимации
const durationPerSegment = 2000;
animateTravel(marker, route, durationPerSegment);

Укажите следующие параметры функции animateTravel:

  • marker: объект маркера, для которого будет добавлена анимация.
  • route: массив координат маршрута в формате [долгота, широта].
  • durationPerSegment: длительность каждого сегмента в миллисекундах.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>2GIS Map API - Courier Animation</title>
    <style>
        html,
        body,
        #container {
            margin: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
        }
    </style>
</head>
<body>
<div id="container"></div>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
    const map = new mapgl.Map('container', {
        center: [55.280, 25.249],
        zoom: 14,
        key: 'Your API access key',
    });
    const route  = `55.280324 25.249851,55.272991 25.239421,55.278568 25.235182,55.284862 25.246507,55.280324 25.249851`.split(',')
        .map(pair => pair.split(' ').map(Number));

    const marker = new mapgl.Marker(map, {
        coordinates: route[0],
        icon: 'https://disk.2gis.com/styles/assets/icons/poi_cars-66605ef515e3adca7dea4e3f1bac00195e250a1250a5bf9e313708cb25fd1467.svg',
        size: [20,20],
    });

    function interpolateCoordinates(coord1, coord2, t) {
        return [
            coord1[0] + (coord2[0] - coord1[0]) * t,
            coord1[1] + (coord2[1] - coord1[1]) * t,
        ];
    }

    function animateCourier(marker, route, durationPerSegment) {
        let segmentIndex = 0;

        function animateSegment(startTime) {
            const elapsedTime = performance.now() - startTime;
            const t = elapsedTime / durationPerSegment;

            if (t < 1) {
                // Интерполируем координаты
                const newCoords = interpolateCoordinates(
                    route[segmentIndex],
                    route[segmentIndex + 1],
                    t
                );
                marker.setCoordinates(newCoords);
                requestAnimationFrame(() => animateSegment(startTime));
            } else {
                segmentIndex++;
                if (segmentIndex < route.length - 1) {
                    animateSegment(performance.now());
                } else {
                    segmentIndex = 0;
                    animateSegment(performance.now());
                }
            }
        }

        if (route.length > 1) {
            animateSegment(performance.now());
        }
    }

    const durationPerSegment = 2000;
    animateCourier(marker, route, durationPerSegment);

    window.addEventListener('unload', () => map.destroy());
</script>
</body>
</html>

Вы можете анимировать движение HTML-маркера при помощи CSS. По примеру ниже вы сможете анимировать движение HTML-маркера по маршруту.

  1. Создайте CSS-анимацию, например:

    <style>
        html,
        body,
        #container {
            margin: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
        }
        .marker_container {
            position: relative;
            width: 40px;
            height: 40px;
        }
        .marker_container::before {
            content: '';
            position: absolute;
            top: 10px;
            left: 10px;
            transform: translate(-50%, -50%);
            width: 80px;
            height: 80px;
            border-radius: 50%;
            background: radial-gradient(
                closest-side,
                rgba(255, 165, 0, 0.8),
                rgba(255, 69, 0, 0.5),
                transparent
            );
            animation: firePulse 1s infinite ease-in-out;
        }
        @keyframes firePulse {
            0% {
                transform: translate(-50%, -50%) scale(1);
                opacity: 1;
            }
            50% {
                transform: translate(-50%, -50%) scale(1.2);
                opacity: 0.7;
            }
            100% {
                transform: translate(-50%, -50%) scale(1);
                opacity: 1;
            }
        }
        .wave {
            position: absolute;
            top: 10px;
            left: 10px;
            width: 10px;
            height: 10px;
            background: rgba(0, 0, 255, 0.5);
            border-radius: 50%;
            transform: translate(-50%, -50%);
            animation: waveAnimation 2s infinite;
        }
        @keyframes waveAnimation {
            0% {
                transform: translate(-50%, -50%) scale(1);
                opacity: 1;
            }
            100% {
                transform: translate(-50%, -50%) scale(5);
                opacity: 0;
            }
        }
    </style>
    
  2. Создайте HTML-маркер. Анимация движения по маршруту выполняется с помощью изменения географических координат маркера:

    const htmlMarker = new mapgl.HtmlMarker(map, {
        coordinates: [55.323, 25.235],
        html:
            '<div class="marker_container"><div class="wave"></div><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 50 50">\n' +
            '    <circle cx="25" cy="25" r="25" fill="white"/>\n' +
            '</svg></div>\n',
    });
    
  3. Добавьте анимацию движения по координатам с помощью функции animateTravel:

    // Функция для интерполяции между двумя точками
    function interpolateCoordinates(coord1, coord2, t) {
        return [coord1[0] + (coord2[0] - coord1[0]) * t, coord1[1] + (coord2[1] - coord1[1]) * t];
    }
    
    function animateTravel(htmlMarker, route, durationPerSegment) {
        let segmentIndex = 0;
    
        function animateSegment(startTime) {
            const elapsedTime = performance.now() - startTime;
            const t = elapsedTime / durationPerSegment; // Процент завершения сегмента
    
            if (t < 1) {
                // Интерполяция координат
                const newCoords = interpolateCoordinates(
                    route[segmentIndex],
                    route[segmentIndex + 1],
                    t,
                );
                htmlMarker.setCoordinates(newCoords);
    
                // Продолжение анимации текущего сегмента
                requestAnimationFrame(() => animateSegment(startTime));
            } else {
                // Переход к следующему сегменту
                segmentIndex++;
                if (segmentIndex < route.length - 1) {
                    animateSegment(performance.now());
                } else {
                    // Зацикливание маршрута
                    segmentIndex = 0;
                    animateSegment(performance.now());
                }
            }
        }
    
        // Начало анимации первого сегмента
        if (route.length > 1) {
            animateSegment(performance.now());
        }
    }
    
    // Вызов функции анимации
    const durationPerSegment = 2000;
    animateTravel(htmlMarker, route, durationPerSegment);
    

    Укажите следующие параметры функции animateTravel:

    • htmlMarker: объект HTML-маркера, для которого будет добавлена анимация (создан на шаге 2).
    • route: массив координат маршрута в формате [долгота, широта].
    • durationPerSegment: длительность каждого сегмента в миллисекундах.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>2GIS Map API - Courier CSS Animation</title>
    <style>
        html,
        body,
        #container {
            margin: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
        }
        .marker_container {
            position: relative;
            width: 40px;
            height: 40px;
        }
        .marker_container::before {
            content: '';
            position: absolute;
            top: 10px;
            left: 10px;
            transform: translate(-50%, -50%);
            width: 80px;
            height: 80px;
            border-radius: 50%;
            background: radial-gradient(closest-side, rgba(255, 165, 0, 0.8), rgba(255, 69, 0, 0.5), transparent);
            animation: firePulse 1s infinite ease-in-out;
        }
        @keyframes firePulse {
            0% {
                transform: translate(-50%, -50%) scale(1);
                opacity: 1;
            }
            50% {
                transform: translate(-50%, -50%) scale(1.2);
                opacity: 0.7;
            }
            100% {
                transform: translate(-50%, -50%) scale(1);
                opacity: 1;
            }
        }
        .wave {
            position: absolute;
            top: 10px;
            left: 10px;
            width: 10px;
            height: 10px;
            background: rgba(0, 0, 255, 0.5);
            border-radius: 50%;
            transform: translate(-50%, -50%);
            animation: waveAnimation 2s infinite;
        }
        @keyframes waveAnimation {
            0% {
                transform: translate(-50%, -50%) scale(1);
                opacity: 1;
            }
            100% {
                transform: translate(-50%, -50%) scale(5);
                opacity: 0;
            }
        }

    </style>
</head>
<body>
<div id="container"></div>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
    const map = new mapgl.Map('container', {
        center: [55.280, 25.249],
        zoom: 14,
        key: 'Your API access key',
    });
    const route  = `55.280324 25.249851,55.272991 25.239421,55.278568 25.235182,55.283162 25.246507,55.280324 25.249851`.split(',')
        .map(pair => pair.split(' ').map(Number));

    const htmlMarker = new mapgl.HtmlMarker(map, {
        coordinates: [55.323, 25.235],

        html: '<div class="marker_container"><div class="wave"></div><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 50 50">\n' +
            '    <circle cx="25" cy="25" r="25" fill="white"/>\n' +
            '</svg></div>\n',
    });

    function interpolateCoordinates(coord1, coord2, t) {
        return [
            coord1[0] + (coord2[0] - coord1[0]) * t,
            coord1[1] + (coord2[1] - coord1[1]) * t,
        ];
    }

    function animateCourier(marker, route, durationPerSegment) {
        let segmentIndex = 0;

        function animateSegment(startTime) {
            const elapsedTime = performance.now() - startTime;
            const t = elapsedTime / durationPerSegment;

            if (t < 1) {
                const newCoords = interpolateCoordinates(
                    route[segmentIndex],
                    route[segmentIndex + 1],
                    t
                );
                marker.setCoordinates(newCoords);
                requestAnimationFrame(() => animateSegment(startTime));
            } else {
                segmentIndex++;
                if (segmentIndex < route.length - 1) {
                    animateSegment(performance.now());
                } else {
                    segmentIndex = 0;
                    animateSegment(performance.now());
                }
            }
        }

        if (route.length > 1) {
            animateSegment(performance.now());
        }
    }

    const durationPerSegment = 2000;
    animateCourier(htmlMarker, route, durationPerSegment);

    window.addEventListener('unload', () => map.destroy());
</script>
</body>
</html>

Вы можете анимировать HTML-маркер при помощи Lottie:

  1. Подключите Lottie:

    <script src="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.0/lottie.min.js"></script>
    
  2. Создайте HTML-маркер:

    const markerHtml = `
            <div class="marker_container">
                <div class="lottie_animation" id="lottie"></div>
            </div>
        `;
    
    const htmlMarker = new mapgl.HtmlMarker(map, {
        coordinates: [55.280324, 25.249851],
        html: markerHtml,
    });
    
  3. Создайте анимацию с помощью Lottie:

// Используем MutationObserver для отслеживания появления элемента. Вместо этого можно также использовать setTimeout со значением 0.
const observer = new MutationObserver(() => {
    const lottieContainer = document.getElementById('lottie');
    if (lottieContainer) {
        observer.disconnect(); // Отключаем наблюдение, когда элемент найден
        lottie.loadAnimation({
            container: lottieContainer, // ID контейнера
            renderer: 'svg',
            loop: true,
            autoplay: true,
            path: '<your>.json', // Путь к JSON
        });
    }
});

observer.observe(document.body, { childList: true, subtree: true });
<!DOCTYPE html>
<html lang="en">
<head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.0/lottie.min.js"></script>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>2GIS Map API - Lottie Animation</title>
    <style>
        html, body, #container {
            margin: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
        }
        .marker_container {
            right: 20px;
            bottom: 20px;
            position: relative;
            width: 40px;
            height: 40px;
        }
        .lottie_animation {
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
<div id="container"></div>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
    const map = new mapgl.Map('container', {
        center: [55.280, 25.249],
        zoom: 14,
        key: 'Your API access key',
    });

    const markerHtml = `
        <div class="marker_container">
            <div class="lottie_animation" id="lottie"></div>
        </div>
    `;

    const htmlMarker = new mapgl.HtmlMarker(map, {
        coordinates: [55.280324, 25.249851],
        html: markerHtml,
    });

    const observer = new MutationObserver(() => {
        const lottieContainer = document.getElementById('lottie');
        if (lottieContainer) {
            observer.disconnect();
            lottie.loadAnimation({
                container: lottieContainer,
                renderer: 'svg',
                loop: true,
                autoplay: true,
                path: 'https://lottie.host/7401522f-2d8b-4049-ad18-eb0edb6af224/CE9lFrNlEH.json',
            });
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });

    window.addEventListener('unload', () => map.destroy());
</script>
</body>
</html>