Mapbox GL JS опирается на WebGL как основной механизм отрисовки, однако поверх него предусмотрен слой абстракции для работы с 2D-canvas через пользовательские слои. Это позволяет интегрировать произвольную графику, генерируемую через HTMLCanvasElement, в систему координат карты, синхронизируя её с масштабированием, вращением и перемещением сцены.
Внутри рендеринга карты существует несколько стадий:
Canvas-интеграция реализуется через интерфейс CustomLayerInterface, который предоставляет жизненный цикл слоя:
onAddrenderonRemoveКаждый из этих методов привязан к моментам обновления сцены.
Canvas-слой создаётся как объект, реализующий интерфейс пользовательского слоя:
const canvasLayer = {
id: 'custom-canvas-layer',
type: 'custom',
renderingMode: '2d',
onAdd: function (map, gl) {
this.map = map;
this.canvas = document.createElement('canvas');
this.canvas.width = map.getCanvas().width;
this.canvas.height = map.getCanvas().height;
this.context = this.canvas.getContext('2d');
},
render: function (gl, matrix) {
const ctx = this.context;
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
ctx.beginPath();
ctx.arc(200, 200, 50, 0, Math.PI * 2);
ctx.fill();
this.map.triggerRepaint();
}
};
В данном примере canvas существует как независимый буфер, который затем может быть композитно наложен поверх карты.
Основная сложность интеграции заключается в необходимости преобразования координат из географической системы (longitude/latitude) в пиксельные координаты.
Mapbox GL JS предоставляет методы:
map.project(lngLat) — перевод географических координат
в экранныеmap.unproject(point) — обратное преобразованиеrender: function (gl) {
const ctx = this.context;
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
const lngLat = [69.589, 42.315]; // координаты
const point = this.map.project(lngLat);
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(point.x, point.y, 8, 0, Math.PI * 2);
ctx.fill();
this.map.triggerRepaint();
}
В этом случае canvas становится экранным представлением географических данных, а вся геометрия привязывается к пиксельной сетке карты.
При изменении размеров контейнера карты необходимо синхронизировать canvas:
onAdd: function (map) {
this.map = map;
this.canvas = document.createElement('canvas');
this.context = this.canvas.getContext('2d');
this.resize();
},
resize: function () {
const canvas = this.map.getCanvas();
this.canvas.width = canvas.width;
this.canvas.height = canvas.height;
}
Без такой синхронизации графика будет искажаться при изменении масштаба окна.
В ряде случаев требуется не только позиционирование точек, но и
работа с матрицей камеры, передаваемой в
render(matrix).
Матрица представляет собой 4×4 transformation matrix, используемую WebGL для преобразования координат мира в clip space.
При использовании 2D canvas обычно применяется обходной путь:
вычисление координат через map.project, однако для высокой
плотности данных возможно использование матричного умножения.
Canvas-слой часто используется для динамических визуализаций:
Пример простого анимационного слоя:
const layer = {
id: 'particles',
type: 'custom',
renderingMode: '2d',
onAdd: function (map) {
this.map = map;
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
this.particles = Array.from({ length: 1000 }, () => ({
lng: 69 + Math.random(),
lat: 42 + Math.random(),
speed: Math.random() * 0.01
}));
},
render: function () {
const ctx = this.ctx;
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (let p of this.particles) {
p.lat += p.speed;
const point = this.map.project([p.lng, p.lat]);
ctx.fillStyle = 'rgba(0, 200, 255, 0.6)';
ctx.fillRect(point.x, point.y, 2, 2);
}
this.map.triggerRepaint();
}
};
Подобные конструкции используют постоянный цикл перерисовки, синхронизированный с рендерингом карты.
На устройствах с высоким DPI требуется учитывать
devicePixelRatio:
const ratio = window.devicePixelRatio;
this.canvas.width = map.getCanvas().width * ratio;
this.canvas.height = map.getCanvas().height * ratio;
this.canvas.style.width = map.getCanvas().width + 'px';
this.canvas.style.height = map.getCanvas().height + 'px';
this.ctx.scale(ratio, ratio);
Без масштабирования изображение становится размытым при высокой плотности экрана.
Mapbox GL JS допускает совместное использование canvas и WebGL-слоёв, где canvas применяется для:
При этом WebGL продолжает отвечать за основную географическую сцену, а canvas действует как overlay.
Для полигонов используется проекция каждой вершины:
const polygon = [
[69.58, 42.31],
[69.60, 42.31],
[69.60, 42.33],
[69.58, 42.33]
];
ctx.beginPath();
polygon.forEach((coord, i) => {
const p = this.map.project(coord);
if (i === 0) ctx.moveTo(p.x, p.y);
else ctx.lineTo(p.x, p.y);
});
ctx.closePath();
ctx.stroke();
Каждая точка преобразуется в экранные координаты отдельно, что делает canvas зависимым от частоты обновления карты.
Метод map.triggerRepaint() играет ключевую роль в
анимации. Он принудительно запускает новый цикл рендеринга даже при
отсутствии событий взаимодействия пользователя.
Без этого вызова динамические canvas-слои перестают обновляться.
Несмотря на гибкость, существуют ограничения:
При масштабировании до десятков тысяч объектов предпочтение обычно отдаётся WebGL-слоям.
Canvas может реагировать на события карты:
this.map.on('move', () => this.render());
this.map.on('zoom', () => this.render());
Каждое изменение камеры требует пересчёта всех координат, поскольку экранная проекция меняется динамически.
Для повышения производительности применяется предварительная отрисовка:
Такой подход снижает нагрузку на главный render thread.
При работе с глобальными координатами возникает проблема округления на больших zoom-уровнях. Mapbox GL JS использует сферическую проекцию Web Mercator, где:
Это влияет на точность canvas-оверлеев, особенно при работе с геометрией высокой плотности.
Canvas сам по себе не знает о географических координатах, поэтому обработка кликов требует обратного преобразования:
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const point = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
const lngLat = this.map.unproject(point);
});
Такой механизм позволяет привязывать интерактивные элементы к географии карты без использования стандартных слоёв Mapbox.
Canvas-слой в контексте Mapbox GL JS часто используется как промежуточный уровень между сырыми данными и WebGL-рендерингом, особенно при необходимости:
Такая архитектура позволяет комбинировать низкоуровневую гибкость canvas с географической системой координат карты.