Canvas renderer

В Leaflet отрисовка векторных слоёв традиционно выполняется через SVG, однако для сценариев с большим количеством объектов или высокой динамикой обновления используется Canvas-рендерер, позволяющий существенно снизить нагрузку на DOM и ускорить перерисовку сцены.

Canvas-рендерер в Leaflet реализован через класс L.Canvas, который наследуется от базового L.Renderer и предоставляет API для отрисовки геометрий на HTML5 <canvas> вместо SVG-узлов. Основное отличие заключается в том, что вся графика рисуется в одном растровом контексте, а не как набор отдельных DOM-элементов.


Внутри Leaflet рендерер Canvas работает как слой абстракции между векторными объектами (L.Path и его наследниками) и HTML5 Canvas API.

Каждый векторный слой (Polyline, Polygon, Circle, Rectangle) при выборе Canvas-рендерера передаёт геометрию в L.Canvas, который преобразует координаты географической системы в пиксельные и выполняет отрисовку через CanvasRenderingContext2D.

Ключевые элементы архитектуры:

  • L.Canvas — основной рендерер
  • L.Renderer — базовый абстрактный класс
  • L.Path — базовый класс для векторных слоёв
  • renderer._ctx — 2D контекст canvas
  • renderer._update() — перерисовка сцены

Инициализация Canvas-рендерера

Canvas-рендерер можно создать явно и передать в слой карты:

const canvasRenderer = L.canvas();

const map = L.map('map', {
    center: [55.75, 37.61],
    zoom: 10
});

После этого рендерер применяется к слоям через опцию renderer:

const polyline = L.polyline([
    [55.75, 37.61],
    [55.80, 37.70],
    [55.85, 37.65]
], {
    renderer: canvasRenderer,
    color: 'blue',
    weight: 3
}).addTo(map);

Если рендерер не указан явно, Leaflet может автоматически выбрать Canvas при определённых условиях, но в большинстве случаев выбор задаётся вручную.


Canvas против SVG в Leaflet

Разница между Canvas и SVG-рендерерами критична при проектировании карт с высокой нагрузкой.

SVG-рендерер:

  • каждый объект — отдельный DOM-элемент
  • удобен для стилизации через CSS
  • хуже масштабируется при тысячах объектов
  • поддерживает события на уровне элементов

Canvas-рендерер:

  • все объекты рисуются в одном canvas
  • минимальная нагрузка на DOM
  • высокая производительность при больших наборах данных
  • события обрабатываются через пространственные индексы Leaflet

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

Canvas становится предпочтительным при:

  • десятках тысяч полилиний
  • динамическом обновлении координат
  • анимации объектов
  • визуализации потоковых данных (GPS, IoT)

В отличие от SVG, где каждый update может приводить к перерасчёту DOM-дерева, Canvas выполняет перерисовку единого буфера.

Внутренний цикл выглядит упрощённо следующим образом:

  1. сбор всех видимых слоёв
  2. преобразование координат в пиксели
  3. очистка canvas (clearRect)
  4. последовательная отрисовка всех геометрий
  5. применение стилей (stroke, fill)

Настройки L.Canvas

Canvas-рендерер поддерживает набор опций, влияющих на качество и поведение отрисовки:

const renderer = L.canvas({
    padding: 0.5,
    tolerance: 10
});

padding

Определяет дополнительную область вокруг viewport, в которой заранее отрисовываются объекты.

  • увеличивает плавность при панорамировании
  • снижает вероятность «пустых зон» при быстром движении карты
  • увеличивает нагрузку на перерисовку

tolerance

Используется для определения чувствительности при hit-testing (определение попадания курсора в линию).

  • большее значение упрощает взаимодействие
  • меньшее значение повышает точность

Использование Canvas для различных типов слоёв

Canvas-рендерер работает с любыми наследниками L.Path:

  • L.Polyline
  • L.Polygon
  • L.Circle
  • L.Rectangle
  • L.GeoJSON (векторная часть)

Пример отрисовки GeoJSON через Canvas:

L.geoJSON(data, {
    renderer: L.canvas(),
    style: function (feature) {
        return {
            color: '#3388ff',
            weight: 2,
            fillOpacity: 0.3
        };
    }
}).addTo(map);

Перерисовка и жизненный цикл

Canvas не хранит отдельные DOM-узлы для каждого объекта, поэтому обновление состояния происходит через централизованный механизм.

Основные этапы:

  • layer.redraw() — принудительная перерисовка слоя
  • map.on('move zoom', ...) — триггер обновления сцены
  • renderer._redraw() — внутренний вызов Canvas очистки и отрисовки

При изменении стиля или геометрии слой помечается как «dirty» и включается в следующий цикл рендера.


Работа с событиями

Canvas не может назначать события на каждый объект напрямую, поэтому используется геометрическое определение попадания точки.

Leaflet реализует это через:

  • пространственные индексы
  • проверку расстояния до линии
  • bounding box проверки

Пример обработки клика:

const layer = L.polyline(coords, {
    renderer: L.canvas()
}).addTo(map);

layer.on('click', function (e) {
    console.log('Клик по линии', e.latlng);
});

Несмотря на отсутствие DOM-узлов, события работают прозрачно для разработчика.


Особенности рендеринга линий

Canvas использует методы контекста:

  • ctx.beginPath()
  • ctx.moveTo()
  • ctx.lineTo()
  • ctx.stroke()

Каждый L.Path преобразуется в последовательность этих команд.

Параметры стиля:

  • colorstrokeStyle
  • weightlineWidth
  • opacityglobalAlpha
  • fillColorfillStyle

HiDPI и масштабирование

Canvas-рендерер автоматически адаптируется под устройства с высокой плотностью пикселей.

Механизм включает:

  • масштабирование devicePixelRatio
  • увеличение внутреннего canvas buffer
  • корректировку координат при рисовании

Это предотвращает размытость линий на Retina-дисплеях и аналогичных экранах.


Ограничения Canvas-рендерера

Несмотря на высокую производительность, Canvas имеет ряд ограничений:

  • невозможность стилизации отдельных элементов через CSS
  • отсутствие DOM-структуры
  • сложность реализации сложных интерактивных эффектов
  • необходимость полной перерисовки при изменении сцены

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


Комбинирование Canvas и SVG

Leaflet допускает смешанный подход:

  • Canvas для тяжёлых векторных слоёв
  • SVG для интерактивных элементов интерфейса

Типичный сценарий:

  • полигоны и линии → Canvas
  • маркеры и popups → DOM/SVG

Это позволяет балансировать между производительностью и удобством взаимодействия.


Внутренние оптимизации Leaflet

Canvas-рендерер включает несколько оптимизаций:

  • кеширование трансформированных координат
  • отрисовка только видимых слоёв
  • группировка операций stroke/fill
  • минимизация state changes контекста

При большом количестве объектов ключевым фактором становится уменьшение числа вызовов ctx.stroke() и ctx.fill().


Геометрические преобразования

Перед отрисовкой Leaflet выполняет преобразование координат:

  • WGS84 → pixel space
  • lat/lng → projected point
  • учитывается zoom level

Упрощённая модель:

pixel = map.project(latlng, zoom)

Canvas затем работает только с пиксельными координатами, не оперируя географическими значениями.


Использование в высоконагруженных сценах

Canvas-рендерер применяется в системах:

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

В таких задачах количество объектов может достигать десятков и сотен тысяч, что делает SVG непригодным.


Поведение при масштабировании карты

При изменении zoom:

  • происходит полная очистка canvas
  • пересчитываются все координаты
  • выполняется повторная отрисовка всех видимых слоёв

Процесс оптимизируется за счёт ограничения области отрисовки текущим viewport.


Стилизация через динамические функции

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

function style(feature) {
    return {
        color: feature.properties.value > 50 ? 'red' : 'green',
        weight: 2
    };
}

Это позволяет реализовывать тепловые карты и тематическое картографирование без смены рендерера.


Итоговая модель поведения Canvas в Leaflet

Canvas в Leaflet функционирует как высокопроизводительный растровый слой, объединяющий множество векторных объектов в единый графический буфер, с управляемым жизненным циклом, оптимизированной перерисовкой и абстрактной системой взаимодействия с событиями, скрывающей отсутствие DOM-структуры.