Адаптивность отображения в OpenLayers строится вокруг синхронизации
размеров DOM-контейнера и внутреннего viewport карты. Любое изменение
геометрии блока, в котором размещена карта, требует уведомления
экземпляра Map, иначе визуальный слой останется в
устаревших координатах пиксельного пространства.
Карта в OpenLayers опирается на фиксированную привязку к контейнеру. При изменении размеров контейнера необходимо явно вызывать пересчёт:
map.updateSize();
Этот метод инициирует перерасчёт внутренних параметров рендера, включая:
Без вызова updateSize() карта визуально «ломается»:
тайлы остаются в прежнем положении, появляются пустые зоны или
смещения.
Корректная адаптивность начинается не с API карты, а с CSS-структуры:
<div id="map"></div>
html, body {
margin: 0;
height: 100%;
}
#map {
width: 100%;
height: 100vh;
}
Ключевой момент — отсутствие неопределённой высоты. OpenLayers не
вычисляет высоту автоматически, поэтому height: 100% без
заданной высоты родителей приводит к нулевому viewport.
Для более сложных интерфейсов часто используется flex-раскладка:
.layout {
display: flex;
height: 100vh;
}
.sidebar {
width: 300px;
}
.map {
flex: 1;
}
Простейший способ обеспечить адаптивность — подписка на событие
resize:
window.addEventListener('resize', () => {
map.updateSize();
});
Однако этот подход имеет ограничение: он реагирует только на изменение окна браузера, игнорируя изменения DOM-структуры (например, открытие сайдбара или изменение панели инструментов).
Более точный механизм — ResizeObserver, который
отслеживает изменения конкретного контейнера:
const target = document.getElementById('map');
const observer = new ResizeObserver(() => {
map.updateSize();
});
observer.observe(target);
Этот подход корректно работает при:
На мобильных устройствах основная проблема связана с изменением viewport при появлении адресной строки и виртуальной клавиатуры.
Рекомендуемая стратегия:
100dvh вместо 100vh;#map {
height: 100dvh;
}
При необходимости дополнительной стабильности применяется принудительный пересчёт:
window.addEventListener('orientationchange', () => {
setTimeout(() => map.updateSize(), 300);
});
Задержка компенсирует анимацию изменения viewport в мобильных браузерах.
OpenLayers автоматически учитывает devicePixelRatio, что
влияет на чёткость тайлов и векторного рендера. При адаптивной верстке
важно учитывать, что изменение масштабирования интерфейса может
сопровождаться перерасчётом canvas.
Для стабилизации рендеринга используется:
const map = new Map({
target: 'map',
pixelRatio: window.devicePixelRatio
});
В некоторых случаях фиксирование pixelRatio улучшает
производительность на слабых устройствах, но снижает визуальную
чёткость.
При изменении layout (например, открытие боковой панели) важно синхронизировать состояние карты:
function toggleSidebar() {
document.body.classList.toggle('sidebar-open');
requestAnimationFrame(() => {
map.updateSize();
});
}
Использование requestAnimationFrame позволяет дождаться
завершения перерасчёта layout перед обновлением viewport.
Контролы карты (Zoom, Rotate,
Attribution) должны оставаться доступными при любом размере
экрана. На узких устройствах их расположение часто меняется:
import { defaults as defaultControls } from 'ol/control';
const map = new Map({
controls: defaultControls({
attribution: true,
zoom: true,
rotate: false
}),
target: 'map'
});
Для мобильной версии контролы могут быть переупорядочены через CSS:
.ol-control {
transform: scale(0.9);
}
При переходе в полноэкранный режим необходимо учитывать задержку изменения размеров:
document.addEventListener('fullscreenchange', () => {
setTimeout(() => map.updateSize(), 100);
});
Fullscreen меняет размеры контейнера асинхронно, поэтому немедленный вызов обновления часто даёт неверный результат.
При активной анимации интерфейса или частых изменениях DOM полезно ограничивать частоту вызова пересчёта:
function debounce(fn, delay) {
let t;
return () => {
clearTimeout(t);
t = setTimeout(fn, delay);
};
}
const safeUpdate = debounce(() => map.updateSize(), 100);
window.addEventListener('resize', safeUpdate);
Это снижает нагрузку на рендерер и предотвращает избыточные пересчёты матриц.
Если карта встроена в интерфейс с плавными переходами (например, CSS transition для панели), обновление размера должно происходить после завершения анимации:
sidebar.addEventListener('transitionend', () => {
map.updateSize();
});
Без этого карта часто фиксирует промежуточное состояние контейнера.
При изменении масштаба экрана может потребоваться адаптация источников данных:
Пример динамической смены tile layer:
layer.setSource(new XYZ({
url: isMobile
? 'https://tiles-mobile.example/{z}/{x}/{y}.png'
: 'https://tiles-desktop.example/{z}/{x}/{y}.png'
}));
View карты должен сохранять согласованность при любых
изменениях размеров:
const view = new View({
center: [0, 0],
zoom: 2,
constrainResolution: true
});
Параметр constrainResolution предотвращает появление
дробных уровней масштаба при резких изменениях контейнера.
На дисплеях с высокой плотностью пикселей важно избегать двойного масштабирования:
Конфликт этих механизмов приводит к размытию или пересчёту тайлов. Поэтому адаптивная логика должна учитывать только один уровень масштабирования — через OpenLayers, а не через CSS transform.
Стабильная схема адаптивной карты включает три уровня:
CSS-уровень
DOM-уровень
OpenLayers-уровень
map.updateSize()Совокупность этих механизмов обеспечивает непрерывную корректную работу карты при любых изменениях интерфейса, независимо от платформы и типа устройства.