Яндекс карта при переходе в одностраничном приложении на верно отображается карта

В приложение Blazor Server На странице отображается карта.

введите сюда описание изображения

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

API Карта v.2.1

введите сюда описание изображения

API Карта v.3 - отображается просто бежевый квадрат.

Код для версии 2.1

App.razor

<script src="https://api-maps.yandex.ru/2.1/?apikey=КЛЮЧ API&lang=ru_RU" 
        type="text/javascript"></script>

site.js

window.map = {
    start: function (address) {
        ymaps.ready(init);

        function init() {
            var myMap = new ymaps.Map("map", {
                center: [55.76, 37.64],
                zoom: 10
            });

            ymaps.geocode(address).then(function (res) {
                var firstGeoObject = res.geoObjects.get(0);
                var coords = firstGeoObject.geometry.getCoordinates();
                var bounds = firstGeoObject.properties.get('boundedBy');

                myMap.geoObjects.add(firstGeoObject);
                myMap.setBounds(bounds, {
                    checkZoomRange: true
                });
            });
        }
    }
}

Map.razor

@inject IJSRuntime JS

<div id="map" style="width: 600px; height: 400px;"></div>

@code {
    [Parameter]
    public string Address { get; set; }

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync("map.start", Address);
        }
    }
}

Как сделать что бы при переходе отображалась правильная карта?


Ответы (3 шт):

Автор решения: YaSupport

Если код один и тот же, но при переходе внутри сайта карта 2.1 перестаёт отрабатывать тарифицируемые запросы, а 3.0 вовсе не загружается, вам следует убедиться, что при таких переходах в запросах API корректно передаётся заголовок referer и нет ошибок в консоли. Без соответствия его прописанному в настройках ключа домену авторизации в сервисе не будет, и API вернёт ошибку 403.

Для проверки нужен доступ к странице с картой. Если она закрытая, с проверкой поможет поддержка API Яндекс Карт, убедившись, что ключ используется коммерческий.

→ Ссылка
Автор решения: Dev18

Проблема типична для SPA-навигации в Blazor Server: контейнер карты переотрисовывается без полной перезагрузки страницы, а экземпляр карты либо «забывается», либо инициализируется, пока контейнер ещё не имеет финальных размеров — из-за этого в 2.1 «съезжает», а в 3.0 часто виден «бежевый квадрат».

Для наглядности

введите сюда описание изображения

Когда я обернула карту в отдельный div, задала размеры через CSS — при навигации карта осталась в своём контейнере.

fitToViewport тоже можно дергать, но в данном случае особой разницы нет — и с ним, и без него всё ведёт себя одинаково.

Конечно, также необходимо Dispose-ать сам компонент - но это тоже не поменяло поведения

Для примера можно сравнить с leaflet : там стили карты подключаются отдельно и при перерисовке компонента всё работает без проблем.

Вот решение на гитхаб для Яндекса из того, что вышло

@page "/map"

@rendermode InteractiveServer
@implements IDisposable
@inject IJSRuntime JS


<div class="map-wrap">
    <div id="map"></div>
</div>

@code {

    [Parameter]
    public string Address { get; set; }

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync("map.start", Address);
        }
    }
    public void Dispose() => _ = JS.InvokeVoidAsync("map.dispose"); //обязательно чистимся :)
}
/*yandex map*/
.map-wrap {
    width: 100%;
    height: 500px;
    position: relative;
    overflow: hidden;
    background-color: red;
}

#map {
    width: 100%;
    height: 100%;
}
window.map = {
    _instance: null,
    _onResize: null,

    start(address) {
        ymaps.ready(() => {
            if (this._instance) this.dispose();

            const myMap = new ymaps.Map("map", { center: [55.76, 37.64], zoom: 10 });
            this._instance = myMap;

            const fit = () => myMap.container.fitToViewport();

            requestAnimationFrame(fit);

            ymaps.geocode(address).then(res => {
                const obj = res.geoObjects.get(0);
                const bounds = obj.properties.get("boundedBy");
                myMap.geoObjects.add(obj);
                myMap.setBounds(bounds, { checkZoomRange: true, useMapMargin: true });
                requestAnimationFrame(fit);
            });

            this._onResize = fit;
            window.addEventListener("resize", this._onResize);
        });
    },

    dispose() {
        if (this._onResize) {
            window.removeEventListener("resize", this._onResize);
            this._onResize = null;
        }
        if (this._instance) {
            this._instance.destroy();
            this._instance = null;
        }
    }
};

на демо видно, что при навигации карта возвращается на место

введите сюда описание изображения

Обратите внимание: у кнопок самой карты тоже могут ломаться стили — это из-за глобальных CSS приложения. Если что-то кривится, проверяйте reset/normalize и исключайте .ymaps-* классы - спойлер => у меня не сработало :) как сказано в ответе от эксперта, возможно есть тарификация...


Как сделать что бы при переходе отображалась правильная карта?

  1. менять стили, тестить все кнопки по отдельности, "подгонять"
  2. использовать другую карту
  3. узнать у тех поддержки яндекса про тарификацию
→ Ссылка
Автор решения: Dev18

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

введите сюда описание изображения

/*yandex map*/
.map-wrap {
    position: relative;
    width: 100%;
    height: 500px;
}

.map-frame {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

map.js

window.mapFrame = {
    sendAddress: function (frameId, address) {
        const frame = document.getElementById(frameId);
        if (frame && frame.contentWindow) {
            frame.contentWindow.postMessage({ address }, "*");
        }
    }
};

map-frame.html

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="utf-8" />
    <title>Yandex Map Frame</title>
    <script src="https://api-maps.yandex.ru/2.1/?apikey=АпиКлюч&lang=ru_RU"></script>
    <style>
        html, body, #map {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
    <div id="map"></div>
    <script>
    ymaps.ready(function(){
      const map = new ymaps.Map("map", { center:[55.76,37.64], zoom:10 });

      window.addEventListener("message", (e) => {
        if (!e.data?.address) return;
        ymaps.geocode(e.data.address).then((res) => {
          const obj = res.geoObjects.get(0);
          if (!obj) return;
          map.geoObjects.removeAll();
          map.geoObjects.add(obj);
          map.setBounds(obj.properties.get("boundedBy"), { checkZoomRange:true });
        });
      });
    });
    </script>
</body>
</html>

MapComponent

@page "/map"
@rendermode InteractiveServer
@inject IJSRuntime JS

<div class="map-wrap">
    <iframe id="@_frameId"
            src="map-frame.html"
            frameborder="0"
            class="map-frame"></iframe>
</div>

@code {
    [Parameter] public string Address { get; set; } = "Санкт-Петербург";
    private readonly string _frameId = $"map-frame-{Guid.NewGuid()}";

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync("mapFrame.sendAddress", _frameId, Address);
        }
    }
}

Решение с iframe реально работает и прекрасно спасает стили, но у него есть минусы, которые мне приходят на ум, может и другие ещё есть:

  • Сообщения теряются. postMessage уходит сразу, а фрейм может не успеть загрузиться => адрес не дойдёт.

  • Безопасность. Сейчас сообщения шлются на * => в проде надо что-то додумать.

  • Производительность и UX. iframe тянет отдельный документ, память, стили. На слабых устройствах может лагать...

  • Лишний вес/код/костыль. Нужно следить, чтобы map-frame.html корректно грузился, и отдельно обновлять код внутри фрейма при переходе на API 3.0.

решение на гитхаб на ветке useIframe

→ Ссылка