Skip to content

矢量渲染系统

目的和范围

本文档解释 Leaflet 中的矢量渲染系统,该系统负责将矢量图形(折线、多边形、圆形等)绘制到屏幕上。系统提供两种渲染策略:基于 Canvas 的像素操作渲染和基于 DOM 元素的 SVG 渲染。关于矢量形状类本身(Polyline、Polygon、Circle)的信息,请参阅 矢量图形

渲染系统建立在三个核心组件之上:

  • Renderer 基类:管理渲染器生命周期和坐标转换
  • Canvas 渲染器:基于像素的渲染,具有手动命中检测
  • SVG 渲染器:基于 DOM 的渲染,使用原生浏览器事件

架构概览

SVG
100%

Renderer 基类

Renderer 类扩展 BlanketOverlay,为 Canvas 和 SVG 实现提供基础。它管理路径图层的集合并协调它们的渲染生命周期。

核心职责

职责实现
图层跟踪维护 _layers 对象,将标记的图层 ID 映射到图层实例
坐标更新通过 _onZoomEnd() 在缩放结束时重新计算图层坐标
渲染更新在 'update' 事件上通过 _updatePaths() 触发图层更新
视图重置通过在所有图层上调用 _reset() 处理视图重置

生命周期集成

SVG
100%

渲染器监听地图事件并将其转换为坐标更新。当地图移动或缩放时,渲染器确保所有路径通过 _project() 重新计算其像素位置,并通过 _update() 重新绘制。

Canvas 渲染器

Canvas 渲染器使用 HTML5 Canvas 2D 上下文进行基于像素的绘制。与 SVG 不同,Canvas 不为每个形状创建单独的 DOM 元素,需要自定义实现命中检测和 Z 序管理。

容器初始化

Canvas 渲染器创建 <canvas> 元素并附加指针事件监听器:

SVG
100%

Canvas 通过将内部 canvas 大小缩放 window.devicePixelRatio 同时保持 CSS 显示大小来处理 Retina 显示屏:

绘制顺序管理

Canvas 使用双向链表结构维护绘制顺序。每个图层接收一个包含对前一个和后一个图层引用的 _order 对象:

SVG
100%

这种结构实现了 bringToFront()bringToBack() 的高效重新排序操作:

操作实现复杂度
_initPath()追加到列表末尾O(1)
_bringToFront()移动到 _drawLast 位置O(1)
_bringToBack()移动到 _drawFirst 位置O(1)
_removePath()从列表中取消链接O(1)

渲染管道

Canvas 渲染器使用基于请求的重新绘制系统,具有脏矩形优化:

SVG
100%

_redrawBounds 优化仅清除和重绘 canvas 中发生变化的部分,减少局部更新的渲染成本:

命中检测

Canvas 需要手动命中检测,因为绘制的像素没有单独的标识。渲染器按绘制顺序迭代图层并测试点包含:

SVG
100%

_containsPoint() 方法由每个 Path 子类实现,并包括描边宽度的容差:

渲染器将指针悬停检测限制为 32ms,以避免在快速鼠标移动期间性能下降:

绘制基元

Canvas 渲染器提供两个核心绘制方法:

方法用途关键操作
_updatePoly(layer, closed)绘制折线/多边形ctx.beginPath(), ctx.moveTo(), ctx.lineTo(), 可选 ctx.closePath()
_updateCircle(layer)绘制圆形ctx.arc(), 通过 ctx.scale() 处理椭圆缩放

两个方法都委托给 _fillStroke() 来应用样式:

SVG 渲染器

SVG 渲染器为每个矢量图层创建单独的 SVG <path> 元素,利用浏览器的原生事件处理和渲染。

容器结构

SVG
100%

SVG 容器使用 viewBox 来定位路径而不修改它们的坐标:

Path 元素创建

当图层初始化时,SVG 渲染器创建具有适当类和交互性的 <path> 元素:

SVG
100%

样式应用

SVG 渲染器通过直接在 path 元素上设置 SVG 属性来应用样式:

样式类别SVG 属性
描边stroke, stroke-opacity, stroke-width, stroke-linecap, stroke-linejoin
虚线模式stroke-dasharray, stroke-dashoffset
填充fill, fill-opacity, fill-rule

路径数据生成

SVG 渲染器将坐标转换为 SVG 路径数据字符串:

SVG
100%

对于圆形,渲染器使用 SVG 弧线命令绘制两个半圆:

Z-Index 管理

由于 SVG 不支持 z-index,SVG 渲染器通过重新排序 DOM 元素来管理图层顺序:

操作实现
_bringToFront()使用 DomUtil.toFront() 将 path 元素移动到父元素末尾
_bringToBack()使用 DomUtil.toBack() 将 path 元素移动到父元素开头

事件处理

与 Canvas 不同,SVG 路径接收原生浏览器事件。SVG 渲染器使用 leaflet-interactive 类标记交互式路径,并调用 addInteractiveTarget() 与 Leaflet 的事件系统集成:

Path-Renderer 集成

Path 类通过定义良好的接口与渲染器协调:

SVG
100%

渲染器选择

地图可以指定默认渲染器,或者单个路径可以使用显式渲染器:

// 地图级别默认渲染器
const map = new LeafletMap('map', {
    renderer: new Canvas()
});

// 路径特定渲染器
const myRenderer = new SVG({ padding: 0.5 });
const line = new Polyline(coords, { renderer: myRenderer });

渲染器比较

方面CanvasSVG
DOM 元素单个 <canvas> 元素每个图层一个 <path>
事件处理通过 _containsPoint() 手动命中检测原生浏览器事件
绘制顺序双向链表DOM 元素顺序
Z-Index 更改O(1) 列表操作 + 重绘O(1) DOM 重新排序
渲染即时模式(清除 + 重绘)保留模式(浏览器管理)
内存固定 canvas 大小随图层数量扩展
命中检测O(n) 遍历图层O(1) 浏览器原生
样式更新需要重绘仅属性更改
部分更新脏矩形优化逐元素失效

性能考虑

Canvas 优化

  1. 重绘边界:仅清除和重绘受影响的矩形

  2. 请求动画帧:将多个更新请求批处理到单个帧中

  3. 悬停节流:将指针移动处理限制为 32ms 间隔

  4. 设备像素比:以设备分辨率渲染以支持 Retina 显示屏

SVG 优化

  1. ViewBox 更新:一起移动所有路径而不重新计算坐标

  2. 属性更新:更改样式而不完全重新渲染

  3. 原生事件:利用浏览器内置的事件处理

通用优化

两个渲染器都:

  • 使用 padding 扩展容器边界以减少边缘裁剪
  • 在缩放动画期间延迟更新
  • 在缩放结束时批量更新坐标

坐标转换

渲染器的坐标系统使用相对于地图像素原点的像素坐标。当地图移动时,渲染器更新其变换而不是重新计算所有图层坐标:

Canvas 变换:

// 将 canvas 上下文平移以与地图边界对齐
this._ctx.setTransform(
    s, 0, 0, s,
    -b.min.x * s,
    -b.min.y * s
);

SVG ViewBox:

// 更新 viewBox 以一起移动所有路径
this._container.setAttribute('viewBox', 
    [b.min.x, b.min.y, size.x, size.y].join(' ')
);