Яндекс карта при переходе в одностраничном приложении на верно отображается карта
В приложение 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 шт):
Если код один и тот же, но при переходе внутри сайта карта 2.1 перестаёт отрабатывать тарифицируемые запросы, а 3.0 вовсе не загружается, вам следует убедиться, что при таких переходах в запросах API корректно передаётся заголовок referer и нет ошибок в консоли. Без соответствия его прописанному в настройках ключа домену авторизации в сервисе не будет, и API вернёт ошибку 403.
Для проверки нужен доступ к странице с картой. Если она закрытая, с проверкой поможет поддержка API Яндекс Карт, убедившись, что ключ используется коммерческий.
Проблема типична для 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-*
классы - спойлер => у меня не сработало :) как сказано в ответе от эксперта, возможно есть тарификация...
Как сделать что бы при переходе отображалась правильная карта?
- менять стили, тестить все кнопки по отдельности, "подгонять"
- использовать другую карту
- узнать у тех поддержки яндекса про тарификацию
можно отдельно вынести яндекс карту в 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