Анимация линии

Open in CodeSandbox

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

Чтобы добавить новые точки в геометрию ломаной линии, будем использовать метод set. На вход методу будем передавать индекс новой точки и ее координаты.

Также геометрию ломаной линии можно поменять целиком с помощью метода setCoordinates.

В примере показано, как на основе класса линии Polyline создать модуль для анимирования линии на карте. Для этого достаточно создать экземпляр класса AnimatedLine и задать длительность анимации через опцию animationTime.

<!DOCTYPE html>
<html>
    <head>
        <title>Анимация линии</title>
        <meta
            http-equiv="Content-Type"
            content="text/html; charset=utf-8"
        />
        <!--
        Укажите свой API-ключ. Тестовый ключ НЕ БУДЕТ работать на других сайтах.
        Получить ключ можно в Кабинете разработчика: https://developer.tech.yandex.ru/keys/
    -->
        <script
            src="https://api-maps.yandex.ru/2.1/?lang=ru_RU&amp;apikey=<ваш API-ключ>"
            type="text/javascript"
        ></script>
        <script src="polyline_animation.js" type="text/javascript"></script>
        <script src="animated_line.js" type="text/javascript"></script>
        <style>
            html,
            body,
            #map {
                width: 100%;
                height: 100%;
                padding: 0;
                margin: 0;
            }
        </style>
    </head>
    <body>
        <div id="map"></div>
    </body>
</html>
ymaps.modules.define(
    "AnimatedLine",
    ["util.defineClass", "Polyline", "vow"],
    function (provide, defineClass, Polyline, vow) {
        /**
         * @fileOverview Анимированная линия.
         */
        /**
         * Создает экземпляр анимированной линии.
         * @class AnimatedLine. Представляет собой геообъект с геометрией geometry.LineString.
         * @param {Boolean} [options.animationTime = 4000] Длительность анимации.
         **/
        function AnimatedLine(geometry, properties, options) {
            AnimatedLine.superclass.constructor.call(
                this,
                geometry,
                properties,
                options
            );
            this._loopTime = 50;
            this._animationTime = this.options.get("animationTime", 4000);
            // Вычислим длину переданной линии.
            var distance = 0;
            var previousElem = geometry[0];
            this.geometry.getCoordinates().forEach(function (elem) {
                distance += getDistance(elem, previousElem);
                previousElem = elem;
            });
            // Вычислим минимальный интервал отрисовки.
            this._animationInterval =
                (distance / this._animationTime) * this._loopTime;
            // Создадим массив с более частым расположением промежуточных точек.
            this._smoothCoords = generateSmoothCoords(
                geometry,
                this._animationInterval
            );
        }
        defineClass(AnimatedLine, Polyline, {
            // Анимировать линию.
            start: function () {
                var value = 0;
                var coords = this._smoothCoords;
                var line = this;
                var loopTime = this._loopTime;
                // Будем добавлять по одной точке каждые 50 мс.
                function loop(value, currentTime, previousTime) {
                    if (value < coords.length) {
                        if (
                            !currentTime ||
                            currentTime - previousTime > loopTime
                        ) {
                            line.geometry.set(value, coords[value]);
                            value++;
                            previousTime = currentTime;
                        }
                        requestAnimationFrame(function (time) {
                            loop(value, time, previousTime || time);
                        });
                    } else {
                        // Бросаем событие окончания отрисовки линии.
                        line.events.fire("animationfinished");
                    }
                }

                loop(value);
            },
            // Убрать отрисованную линию.
            reset: function () {
                this.geometry.setCoordinates([]);
            },
            // Запустить полный цикл анимации.
            animate: function () {
                this.reset();
                this.start();
                var deferred = vow.defer();
                this.events.once("animationfinished", function () {
                    deferred.resolve();
                });
                return deferred.promise();
            },
        });
        // Функция генерации частых координат по заданной линии.
        function generateSmoothCoords(coords, interval) {
            var smoothCoords = [];
            smoothCoords.push(coords[0]);
            for (var i = 1; i < coords.length; i++) {
                var difference = [
                    coords[i][0] - coords[i - 1][0],
                    coords[i][1] - coords[i - 1][1],
                ];
                var maxAmount = Math.max(
                    Math.abs(difference[0] / interval),
                    Math.abs(difference[1] / interval)
                );
                var minDifference = [
                    difference[0] / maxAmount,
                    difference[1] / maxAmount,
                ];
                var lastCoord = coords[i - 1];
                while (maxAmount > 1) {
                    lastCoord = [
                        lastCoord[0] + minDifference[0],
                        lastCoord[1] + minDifference[1],
                    ];
                    smoothCoords.push(lastCoord);
                    maxAmount--;
                }
                smoothCoords.push(coords[i]);
            }
            return smoothCoords;
        }
        // Функция нахождения расстояния между двумя точками на плоскости.
        function getDistance(point1, point2) {
            return Math.sqrt(
                Math.pow(point2[0] - point1[0], 2) +
                    Math.pow(point2[1] - point1[1], 2)
            );
        }
        provide(AnimatedLine);
    }
);
ymaps.ready(["AnimatedLine"]).then(init);

function init(ymaps) {
    // Создаем карту.
    var myMap = new ymaps.Map(
        "map",
        {
            center: [55.762, 37.57835813659775],
            zoom: 16,
        },
        {
            searchControlProvider: "yandex#search",
        }
    );
    // Создаем ломаные линии.
    var firstAnimatedLine = new ymaps.AnimatedLine(
        [
            [55.76028460519329, 37.57704491961252],
            [55.76068242105497, 37.57704760182153],
            [55.76070813128414, 37.57682497847331],
            [55.76074594041378, 37.57683302510035],
            [55.76070753602857, 37.5772156971609],
            [55.76099639684956, 37.577322985521505],
            [55.760916500352934, 37.578192870439906],
            [55.76091952506843, 37.5785147355217],
            [55.76098304403938, 37.57876954537813],
            [55.76109495816394, 37.57893852454606],
            [55.76122432960969, 37.578535038979666],
        ],
        {},
        {
            // Задаем цвет.
            strokeColor: "#ED4543",
            // Задаем ширину линии.
            strokeWidth: 5,
            // Задаем длительность анимации.
            animationTime: 4000,
        }
    );
    var secondAnimatedLine = new ymaps.AnimatedLine(
        [
            [55.761223661714205, 37.57854299428123],
            [55.76129474190374, 37.57836060406823],
            [55.76149285834102, 37.57855640532632],
            [55.76173267134118, 37.57864573959325],
            [55.761782872763874, 37.578559582240004],
            [55.7622647306412, 37.57857741008619],
            [55.76247342821094, 37.57840038429122],
            [55.762818964832924, 37.57765342764373],
            [55.76292179998886, 37.57748713068481],
            [55.762890042102114, 37.577167947812036],
            [55.76292179998886, 37.576878269238435],
            [55.763076052212064, 37.57669587902541],
            [55.76309672830313, 37.57723949881904],
        ],
        {},
        {
            strokeColor: "#1E98FF",
            strokeWidth: 5,
            animationTime: 4000,
        }
    );
    // Добавляем линии на карту.
    myMap.geoObjects.add(firstAnimatedLine);
    myMap.geoObjects.add(secondAnimatedLine);
    // Создаем метки.
    var firstPoint = new ymaps.Placemark(
        [55.7602953585417, 37.57705113964169],
        {},
        {
            preset: "islands#redRapidTransitCircleIcon",
        }
    );
    var secondPoint = new ymaps.Placemark(
        [55.76127880650197, 37.57839413202077],
        {},
        {
            preset: "islands#blueMoneyCircleIcon",
        }
    );
    var thirdPoint = new ymaps.Placemark(
        [55.763105418792314, 37.57724573612205],
        {},
        {
            preset: "islands#blackZooIcon",
        }
    );
    // Функция анимации пути.
    function playAnimation() {
        // Убираем вторую линию.
        secondAnimatedLine.reset();
        // Добавляем первую метку на карту.
        myMap.geoObjects.add(firstPoint);
        // Анимируем первую линию.
        firstAnimatedLine
            .animate()
            // После окончания анимации первой линии добавляем вторую метку на карту и анимируем вторую линию.
            .then(function () {
                myMap.geoObjects.add(secondPoint);
                return secondAnimatedLine.animate();
            })
            // После окончания анимации второй линии добавляем третью метку на карту.
            .then(function () {
                myMap.geoObjects.add(thirdPoint);
                // Добавляем паузу после анимации.
                return ymaps.vow.delay(null, 2000);
            })
            // После паузы перезапускаем анимацию.
            .then(function () {
                // Удаляем метки с карты.
                myMap.geoObjects.remove(firstPoint);
                myMap.geoObjects.remove(secondPoint);
                myMap.geoObjects.remove(thirdPoint);
                // Убираем вторую линию.
                secondAnimatedLine.reset();
                // Перезапускаем анимацию.
                playAnimation();
            });
    }
    // Запускаем анимацию пути.
    playAnimation();
}