Векторная анимация в OpenLayers основана на динамическом изменении визуального представления геометрий без обязательного пересоздания самих объектов данных. В основе лежит разделение модели (Feature и Geometry) и представления (Style), где стиль может вычисляться на каждом кадре отрисовки карты.
Ключевой механизм — повторный расчёт стилей через функции стиля, а также использование событий рендеринга карты и API перерисовки слоёв. Такой подход позволяет создавать анимации пульсации, движения маркеров, изменения толщины линий, эффекты свечения и имитацию потока данных вдоль маршрутов.
Основные источники анимации:
StyleFunction, возвращающая разные стили в зависимости
от времениFeature и вызов
feature.changed()postrender у слоя или картыrequestAnimationFrame для синхронизации с
циклом рендерингаСтиль в OpenLayers может быть функцией, вычисляющей визуальное состояние объекта при каждом рендере. Это фундамент для большинства анимаций.
import Style from 'ol/style/Style.js';
import CircleStyle from 'ol/style/Circle.js';
import Fill from 'ol/style/Fill.js';
const styleFunction = (feature, resolution) => {
const time = Date.now() / 1000;
const radius = 6 + Math.sin(time * 3) * 3;
return new Style({
image: new CircleStyle({
radius: radius,
fill: new Fill({
color: 'rgba(30, 144, 255, 0.7)'
})
})
});
};
При каждом перерисовывании карты функция вызывается заново, а параметры стиля пересчитываются динамически. Это позволяет реализовать:
Для создания стабильных анимационных эффектов важно использовать
единый временной источник, а не Date.now() в каждом
вызове.
let start = performance.now();
const styleFunction = (feature) => {
const elapsed = (performance.now() - start) / 1000;
const scale = 1 + Math.sin(elapsed * 2) * 0.5;
return new Style({
image: new CircleStyle({
radius: 8 * scale,
fill: new Fill({ color: 'rgba(255, 0, 0, 0.6)' })
})
});
};
Такой подход синхронизирует все объекты и позволяет управлять фазой анимации глобально.
Помимо изменения стиля, возможно прямое изменение координат геометрии. Это используется для имитации движения объектов по маршрутам.
function moveFeature(feature, delta) {
const geometry = feature.getGeometry();
const coordinates = geometry.getCoordinates();
coordinates[0] += delta.x;
coordinates[1] += delta.y;
geometry.setCoordinates(coordinates);
}
Для плавности обновления применяется цикл
requestAnimationFrame:
function animate() {
requestAnimationFrame(animate);
vectorSource.getFeatures().forEach((f) => {
moveFeature(f, { x: 0.5, y: 0.2 });
});
}
animate();
Изменение геометрии автоматически вызывает перерасчёт слоя и перерисовку карты.
Событие postrender позволяет рисовать поверх
стандартного рендера, используя canvas контекст. Это особенно полезно
для эффектов частиц, следов и сложной графики.
vectorLayer.on('postrender', function (event) {
const ctx = event.context;
const frameState = event.frameState;
ctx.save();
ctx.globalAlpha = 0.6;
ctx.fillStyle = 'blue';
const pixel = event.vectorContext.getPixelFromCoordinate([0, 0]);
ctx.beginPath();
ctx.arc(pixel[0], pixel[1], 10, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
frameState.animate = true;
});
Установка frameState.animate = true принудительно
удерживает цикл рендеринга активным, что критично для непрерывной
анимации.
Часто применяется эффект движения вдоль линии маршрута. Он
реализуется через смещение штриховки
(stroke.dashOffset).
let offset = 0;
const lineStyle = new Style({
stroke: new Stroke({
color: '#3399CC',
width: 3,
lineDash: [10, 10],
lineDashOffset: offset
})
});
function animateLine() {
offset -= 1;
lineStyle.getStroke().setLineDashOffset(offset);
vectorSource.changed();
requestAnimationFrame(animateLine);
}
animateLine();
Такой подход создаёт иллюзию движения по линии без изменения геометрии.
Эффект пульсирующего маркера строится на увеличении радиуса и изменении прозрачности.
const pulseStyle = (feature) => {
const t = (performance.now() % 2000) / 2000;
return new Style({
image: new CircleStyle({
radius: 10 + t * 20,
stroke: new Stroke({
color: `rgba(0, 150, 255, ${1 - t})`,
width: 2
})
})
});
};
Комбинация изменения радиуса и альфа-канала создаёт эффект расширяющейся волны.
При наличии множества анимируемых объектов требуется единый «тактовый генератор». Он предотвращает рассинхронизацию эффектов.
const clock = {
start: performance.now(),
getTime() {
return (performance.now() - this.start) / 1000;
}
};
const styleFunction = () => {
const t = clock.getTime();
return new Style({
image: new CircleStyle({
radius: 5 + Math.sin(t * 5) * 2
})
});
};
Такой подход позволяет:
Производительность напрямую зависит от количества перерасчётов стилей и перерисовок слоя.
Ключевые приёмы оптимизации:
StyleFunctionfeature.changed() вызововVectorImageLayer для упрощённого
рендерингаПример кэширования:
const cache = new Map();
function getStyle(size) {
if (cache.has(size)) return cache.get(size);
const style = new Style({
image: new CircleStyle({
radius: size,
fill: new Fill({ color: 'rgba(0,0,0,0.5)' })
})
});
cache.set(size, style);
return style;
}
Для больших наборов точек используется WebGL-рендеринг, позволяющий переносить вычисления на GPU. В OpenLayers это реализуется через специализированные слои.
Анимация в таком случае часто задаётся через uniform-параметры времени:
const layer = new WebGLPointsLayer({
style: {
symbol: {
symbolType: 'circle',
size: ['+', 5, ['*', 3, ['sin', ['*', 2, ['time']]]]],
color: 'rgba(255, 0, 0, 0.6)'
}
}
});
Здесь time обновляется автоматически движком рендеринга,
что позволяет строить плавные GPU-эффекты без JavaScript-циклов.
На практике часто используется гибридный подход:
Это позволяет разделить вычислительную нагрузку и визуальную сложность.
feature.getGeometry().translate(1, 0);
feature.setStyle((feature) => {
const speed = feature.get('speed');
return new Style({
image: new CircleStyle({
radius: 4 + speed
})
});
});
Такой подход масштабируется лучше, чем полная анимация через перерисовку геометрий.
OpenLayers прекращает перерисовку, если нет изменений. Для анимации необходимо поддерживать активный цикл.
function keepAlive() {
map.render();
requestAnimationFrame(keepAlive);
}
keepAlive();
Это гарантирует регулярное обновление стилей и отрисовку анимационных эффектов даже при отсутствии событий пользователя.