Анимация является одной из ключевых возможностей интерактивных карт. Плавное перемещение камеры, анимированные маркеры, визуализация потоков данных, изменение стилей и динамическое обновление слоёв позволяют создавать современные геоинформационные интерфейсы. Однако большое количество объектов, частые перерисовки и неэффективная работа с данными способны привести к падению FPS, повышенной нагрузке на процессор и ухудшению пользовательского опыта.
Mapbox GL JS использует аппаратное ускорение через WebGL, что обеспечивает высокую производительность даже при отображении большого количества геоданных. Тем не менее эффективность анимации во многом зависит от архитектуры приложения и способов взаимодействия с API библиотеки.
В основе работы Mapbox GL JS лежит цикл рендеринга, связанный с
браузерным механизмом requestAnimationFrame.
Каждый кадр включает несколько этапов:
Чем больше операций приходится выполнять между кадрами, тем ниже итоговая частота обновления.
Для плавной анимации желательно поддерживать:
При частоте 60 кадров в секунду на подготовку одного кадра остаётся примерно 16,7 миллисекунды.
Любые пользовательские анимации рекомендуется строить через
requestAnimationFrame.
Пример движения маркера:
let start;
function animate(timestamp) {
if (!start) {
start = timestamp;
}
const progress = timestamp - start;
marker.setLngLat([
37.6173 + progress * 0.00001,
55.7558
]);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Преимущества такого подхода:
Использование setInterval() и setTimeout()
для сложной анимации обычно менее эффективно.
Одной из самых дорогостоящих операций является обновление данных
через setData().
Неэффективный вариант:
source.setData(newGeoJson);
Если такой вызов выполняется десятки раз в секунду, библиотеке приходится:
При анимации большого числа объектов подобная операция быстро становится узким местом.
Для динамического изменения свойств объектов предпочтительно использовать механизм Feature State.
Создание состояния:
map.setFeatureState(
{
source: 'cities',
id: 1
},
{
selected: true
}
);
Использование в стиле:
'circle-color': [
'case',
['boolean', ['feature-state', 'selected'], false],
'#ff0000',
'#0000ff'
]
Преимущества:
Добавление и удаление слоя является тяжёлой операцией.
Плохо:
map.removeLayer('route');
map.addLayer(layerConfig);
Лучше изменять свойства существующего слоя:
map.setPaintProperty(
'route',
'line-opacity',
0.5
);
или
map.setLayoutProperty(
'route',
'visibility',
'none'
);
Такой подход сохраняет существующие структуры данных и уменьшает объём перерасчётов.
Mapbox GL JS предоставляет встроенные механизмы анимации камеры.
map.flyTo({
center: [37.6173, 55.7558],
zoom: 12
});
map.easeTo({
center: [37.6173, 55.7558],
duration: 3000
});
map.jumpTo({
center: [37.6173, 55.7558]
});
С точки зрения производительности:
| Метод | Анимация | Нагрузка |
|---|---|---|
| jumpTo | Нет | Минимальная |
| easeTo | Да | Средняя |
| flyTo | Да | Наиболее высокая |
Для частых обновлений положения камеры предпочтительнее использовать
easeTo() или даже прямое изменение координат через
jumpTo().
Не каждая задача требует обновления на каждом кадре.
Например, движение транспорта может обновляться раз в 100–200 миллисекунд.
let lastUpdate = 0;
function animate(timestamp) {
if (timestamp - lastUpdate > 100) {
updateVehicles();
lastUpdate = timestamp;
}
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Такой механизм уменьшает число операций без заметного ухудшения визуального восприятия.
При отображении десятков тысяч объектов производительность анимации может резко снижаться.
Основные причины:
Для оптимизации рекомендуется:
Без кластеризации:
map.addSource('points', {
type: 'geojson',
data: data
});
С кластеризацией:
map.addSource('points', {
type: 'geojson',
data: data,
cluster: true,
clusterRadius: 50
});
Преимущества:
Каждое выражение вычисляется для множества объектов.
Сложное выражение:
[
'case',
['>', ['get', 'population'], 1000000],
'#ff0000',
['>', ['get', 'population'], 500000],
'#00ff00',
'#0000ff'
]
При большом количестве объектов вычисления становятся дорогостоящими.
Если значения редко меняются, полезно заранее вычислять нужные параметры на сервере или во время подготовки данных.
Mapbox GL JS позволяет создавать собственные WebGL-слои.
Пример структуры:
const customLayer = {
id: 'custom',
type: 'custom',
onAdd(map, gl) {
// инициализация
},
render(gl, matrix) {
// отрисовка
}
};
Метод render() вызывается на каждом кадре.
Поэтому внутри него необходимо избегать:
Лучшей практикой является предварительная подготовка ресурсов в
onAdd().
Создание объектов внутри цикла анимации приводит к постоянной работе сборщика мусора.
Неэффективно:
function animate() {
const point = {
lng: 37.6,
lat: 55.7
};
}
Лучше:
const point = {
lng: 37.6,
lat: 55.7
};
function animate() {
point.lng += 0.001;
}
Это уменьшает количество временных объектов и снижает вероятность микрозадержек.
Чем выше уровень масштабирования, тем больше данных приходится отображать.
Для ограничения нагрузки можно использовать:
minzoom
и
maxzoom
Например:
map.addLayer({
id: 'buildings',
type: 'fill-extrusion',
source: 'buildings',
minzoom: 15
});
До достижения указанного масштаба слой не участвует в рендеринге.
Неэффективно:
setInterval(() => {
updateData();
}, 50);
Эффективнее:
map.on('moveend', () => {
updateData();
});
Также полезны события:
zoom
zoomend
move
moveend
rotate
pitch
render
idle
Событийная модель позволяет выполнять обновления только при необходимости.
HTML-маркеры создаются поверх WebGL-карты.
Пример:
new mapboxgl.Marker(element)
Несколько десятков таких объектов обычно не создают проблем, однако сотни и тысячи маркеров начинают существенно нагружать DOM.
Для больших наборов данных предпочтительнее:
Они полностью обрабатываются графическим процессором.
Если необходимо отображать большое количество движущихся объектов, иногда эффективнее рисовать их на Canvas.
Источник Canvas:
map.addSource('canvas-source', {
type: 'canvas',
canvas: 'canvas-id',
coordinates: [...]
});
Преимущества:
Для анализа узких мест используются инструменты браузера.
Позволяет определить:
Во время тестирования полезно отслеживать:
Помогает обнаруживать:
Наиболее распространённые проблемы:
source.setData(...)
new mapboxgl.Marker(...)
['case', ...]
removeLayer()
addLayer()
const obj = {};
render(gl, matrix) {
expensiveCalculation();
}
Каждая из перечисленных ошибок может быть практически незаметной на небольших проектах, но при работе с крупными наборами пространственных данных способна привести к серьёзному ухудшению производительности анимации.
Для небольших наборов данных:
requestAnimationFrame;Для средних проектов:
feature-state;setData;Для крупных геоинформационных систем:
Грамотная организация рендеринга, разумное управление источниками данных и эффективное использование возможностей WebGL позволяют поддерживать плавную анимацию даже при отображении десятков тысяч объектов на карте.