Плавное изменение зума

В библиотеке OpenLayers управление масштабом карты основано не на дискретных «ступенях приближения», а на модели разрешения (resolution) и уровня зума (zoom), между которыми существует прямое преобразование. Это позволяет строить как мгновенные переходы, так и плавные анимации с интерполяцией состояний камеры карты.

Ключевая идея: карта в OpenLayers — это не просто набор тайлов, а камера, которая может изменять масштаб непрерывно, а уже слой тайлов подстраивается под текущий resolution.


Модель zoom и resolution

Каждому уровню zoom соответствует определённое разрешение:

  • высокий zoom → маленькое значение resolution (сильное приближение)
  • низкий zoom → большое значение resolution (отдаление)

В стандартной конфигурации используется логарифмическая зависимость:

  • zoom = 0…n
  • resolution уменьшается в 2 раза с каждым шагом (при стандартном tile grid)
import Map from 'ol/Map.js';
import View from 'ol/View.js';

const view = new View({
  center: [0, 0],
  zoom: 2
});

Внутри View хранится непрерывное значение resolution, даже если zoom кажется дискретным.


Анимация изменения масштаба через view.animate

Основной механизм плавного изменения зума — метод animate.

view.animate({
  zoom: 10,
  duration: 1200
});

Этот вызов не устанавливает zoom мгновенно, а интерполирует текущее состояние камеры до целевого значения.

Характер поведения:

  • интерполяция происходит по времени
  • используется easing-функция (по умолчанию easeInOut)
  • одновременно могут анимироваться center, rotation и resolution

Пример комбинированной анимации:

view.animate({
  center: [1000000, 6000000],
  zoom: 8,
  duration: 1500
});

Easing-функции и характер движения камеры

Плавность определяется не только временем, но и функцией интерполяции.

Типовые функции:

  • linear — равномерное изменение
  • easeIn — ускорение
  • easeOut — замедление
  • easeInOut — ускорение и замедление

Пример:

import { easeOut } from 'ol/easing.js';

view.animate({
  zoom: 12,
  duration: 1000,
  easing: easeOut
});

Easing влияет на восприятие движения: при easeOut приближение кажется «естественным», поскольку камера замедляется перед остановкой.


Плавный зум через MouseWheelZoom

Колёсико мыши является главным источником резких скачков масштаба. Для сглаживания используется настройка interaction.

import MouseWheelZoom from 'ol/interaction/MouseWheelZoom.js';

const map = new Map({
  target: 'map',
  view: view,
  interactions: [
    new MouseWheelZoom({
      duration: 400
    })
  ]
});

Поведение параметров:

  • duration — задаёт длительность интерполяции зума
  • useAnchor — фиксирует точку под курсором при масштабировании
  • timeout — задержка между сериями прокрутки

При увеличении duration движение становится более «кинематографичным», но может снижаться отзывчивость.


Использование anchor-zoom (масштабирование относительно курсора)

Один из ключевых аспектов плавности — сохранение точки фокуса.

new MouseWheelZoom({
  useAnchor: true,
  duration: 300
});

При включённом anchor:

  • координата под курсором остаётся фиксированной
  • карта масштабируется относительно этой точки
  • отсутствует «соскальзывание» центра

Это создаёт эффект естественного приближения к месту интереса.


Плавный программный zoom к координате

Часто требуется не просто изменить масштаб, а «перелететь» к объекту с масштабированием.

view.animate({
  center: [3560000, 5390000],
  zoom: 14,
  duration: 2000
});

При необходимости усиления эффекта добавляется промежуточная стадия:

view.animate({
  zoom: view.getZoom() + 2,
  duration: 500
}, {
  center: [3560000, 5390000],
  zoom: 14,
  duration: 1500
});

Так создаётся эффект ступенчатого приближения с последующим наведением.


Контроль пределов и резкости зума

Плавность масштабирования напрямую зависит от диапазона допустимых значений:

const view = new View({
  center: [0, 0],
  zoom: 5,
  minZoom: 2,
  maxZoom: 18
});

При узком диапазоне переходы становятся резче, поскольку меньше пространства для интерполяции.


ConstrainResolution и сглаживание дискретных уровней

При работе с тайловыми картами важно, чтобы zoom соответствовал дискретным уровням tile grid.

const view = new View({
  constrainResolution: true,
  smoothResolutionConstraint: true
});

Эффект:

  • zoom «прилипает» к доступным resolution
  • между уровнями добавляется плавная интерполяция
  • предотвращаются визуальные разрывы тайлов

smoothResolutionConstraint создаёт промежуточное сглаживание, даже если итоговые уровни дискретны.


Плавность при pinch-zoom (мобильные устройства)

На сенсорных экранах используется PinchZoom interaction:

import PinchZoom from 'ol/interaction/PinchZoom.js';

new PinchZoom({
  duration: 200
});

Особенности поведения:

  • непрерывное изменение resolution
  • высокая частота обновления жеста
  • сильная зависимость от производительности GPU

При слишком высокой нагрузке возможны пропуски кадров, что визуально нарушает плавность.


Интерполяция resolution вместо zoom

Внутренне OpenLayers почти всегда работает через resolution:

view.animate({
  resolution: view.getResolution() * 0.5,
  duration: 1000
});

Это позволяет:

  • реализовывать нелинейные переходы
  • обходить ограничения zoom-ступеней
  • точно контролировать масштаб

Комбинированные анимации pan + zoom

Плавное масштабирование часто сочетается с перемещением камеры:

view.animate({
  center: [2000000, 5000000],
  zoom: 6,
  duration: 1200,
  easing: easeInOut
});

При сложных траекториях используется цепочка анимаций:

view.animate({
  center: [1000000, 4000000],
  duration: 600
}, {
  zoom: 12,
  duration: 900
});

Каждый шаг обрабатывается как отдельная стадия интерполяции.


Производительность при анимации масштаба

Плавный зум требует постоянного пересчёта:

  • tile loading
  • reprojection
  • raster rendering
  • vector styling

Критические факторы:

  • количество слоёв
  • использование WebGL renderer
  • размер tile grid
  • частота событий wheel/pinch

При перегрузке возможны:

  • пропуски кадров
  • «дёргание» анимации
  • задержка отрисовки тайлов

Синхронизация зума с отрисовкой слоёв

Векторные слои реагируют на zoom иначе, чем растровые:

  • raster: перезапрос тайлов при смене resolution
  • vector: перерасчёт стилей и геометрии

При анимации важно учитывать, что промежуточные значения zoom существуют постоянно, поэтому стили могут изменяться плавно:

style: function(feature, resolution) {
  return new Style({
    strokeWidth: Math.max(1, 10 / resolution)
  });
}

Управление ощущением «инерции»

Инерционность создаётся сочетанием:

  • MouseWheelZoom.duration
  • easing функции
  • промежуточных resolution шагов

Дополнительный эффект достигается через увеличение duration при быстром скролле и уменьшение при одиночных событиях.


Тонкая настройка поведения zoom в пользовательских сценариях

В сложных приложениях часто комбинируются параметры:

const view = new View({
  center: [0, 0],
  zoom: 3,
  smoothResolutionConstraint: true,
  constrainResolution: true,
  minZoom: 1,
  maxZoom: 20
});

И взаимодействия:

new MouseWheelZoom({
  duration: 250,
  useAnchor: true
});

Так достигается баланс между:

  • предсказуемостью уровней
  • плавностью переходов
  • контролем пользовательского ввода