Jinuss's blog Jinuss's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

前端可视化
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 框架

  • core模块

  • dom模块

  • control

  • geometry

  • geo

  • layer

    • marker

    • tile

    • vector

      • Renderer
      • Canvas
      • Path
      • Polyline
      • Polygon
      • Rectangle
      • Render.getRenderer
      • SVG
        • 概述
        • 源码分析
          • 源码实现
          • 源码解析
          • 兼容性处理
          • 容器初始化与销毁
          • 更新渲染器视图_update()
          • 路径生命周期管理
          • 路径更新与样式
          • 图层排序
          • 设计亮点
        • 总结
      • SVG.Util
      • SVG.VML
      • CircleMarker
      • Circle
    • Layer
    • LayerGroup
    • FeatureGroup
    • DivOverlay
    • Popup
    • Tooltip
    • ImageOverlay
    • SVGOverlay
    • VideoOverlay
    • GeoJSON
  • Map

  • 《Leaflet源码》笔记
  • layer
  • vector
东流
2025-04-15
目录

SVG

# 概述

Leaflet中的SVG渲染器继承自Renderer基类,负责将矢量图层(如折线、多边形和圆等)渲染为SVG元素。

# 源码分析

# 源码实现

SVG渲染器的源码实现如下:

export var SVG = Renderer.extend({
  _initContainer: function () {
    this._container = create("svg"); // 创建svg/VML元素
    this._container.setAttribute("pointer-events", "none"); //穿透点击事件
    this._rootGroup = create("g"); //创建<g>元素作为子图层容器
    this._container.appendChild(this._rootGroup);
  },
  _destroyContainer: function () {
    DomUtil.remove(this._container); //移除DOM元素
    DomEvent.off(this._container); //移除事件监听
    // 清理引用
    delete this._container;
    delete this._rootGroup;
    delete this._svgSize;
  },
  _update: function () {
    if (this._map._animatingZoom && this._bounds) {
      return;
    }
    // 调用父类方法更新边界和中心
    Renderer.prototype._update.call(this);

    var b = this._bounds,
      size = b.getSize(),
      container = this._container;
    
    // 设置SVG尺寸
    if (!this._svgSize || !this._svgSize.equals(size)) {
      this._svgSize = size;
      container.setAttribute("width", size.x);
      container.setAttribute("height", size.y);
    }
    
    // 设置 viewBox实现高效平移
    DomUtil.setPosition(container, b.min);
    container.setAttribute(
      "viewBox",
      [b.min.x, b.min.y, size.x, size.y].join(" ")
    );

    this.fire("update");
  },
  _initPath: function (layer) {
    var path = (layer._path = create("path")); // 创建<path>元素

    if (layer.options.className) { //自定义类名
      DomUtil.addClass(path, layer.options.className);
    }

    if (layer.options.interactive) { // 开启交互,添加交互类名
      DomUtil.addClass(path, "leaflet-interactive");
    }

    this._updateStyle(layer); // 应用样式
    this._layers[stamp(layer)] = layer; // 存储图层引用
  },
  _addPath: function (layer) {
    if (!this._rootGroup) {
      this._initContainer();
    }
    this._rootGroup.appendChild(layer._path); //添加路径到根组
    layer.addInteractiveTarget(layer._path); // 注册交互事件
  },
  _removePath: function (layer) {
    DomUtil.remove(layer._path); // 移除路径
    layer.removeInteractiveTarget(layer._path); // 解绑事件
    delete this._layers[stamp(layer)]; // 清理引用
  },
  _updatePath: function (layer) {
    layer._project(); // 重新投影坐标(如地图缩放后)
    layer._update(); // 更新路径数据
  },
  _updateStyle: function (layer) {
    var path = layer._path,
      options = layer.options;

    if (!path) {
      return;
    }
    // 描边样式
    if (options.stroke) {
      path.setAttribute("stroke", options.color);
      path.setAttribute("stroke-opacity", options.opacity);
      path.setAttribute("stroke-width", options.weight);
      path.setAttribute("stroke-linecap", options.lineCap);
      path.setAttribute("stroke-linejoin", options.lineJoin);
      
      // 实现与虚线
      if (options.dashArray) {
        path.setAttribute("stroke-dasharray", options.dashArray);
      } else {
        path.removeAttribute("stroke-dasharray");
      }

      if (options.dashOffset) {
        path.setAttribute("stroke-dashoffset", options.dashOffset);
      } else {
        path.removeAttribute("stroke-dashoffset");
      }
    } else {
      path.setAttribute("stroke", "none");
    }
    
    // 填充样式
    if (options.fill) {
      path.setAttribute("fill", options.fillColor || options.color);
      path.setAttribute("fill-opacity", options.fillOpacity);
      path.setAttribute("fill-rule", options.fillRule || "evenodd");
    } else {
      path.setAttribute("fill", "none");
    }
  },
  _updatePoly: function (layer, closed) {
    this._setPath(layer, pointsToPath(layer._parts, closed)); // 生成路径数据
  },
  _updateCircle: function (layer) {
    var p = layer._point,
      r = Math.max(Math.round(layer._radius), 1),
      r2 = Math.max(Math.round(layer._radiusY), 1) || r,
      arc = "a" + r + "," + r2 + " 0 1,0 ";

    var d = layer._empty()
      ? "M0 0"
      : "M" +
        (p.x - r) +
        "," +
        p.y +
        arc +
        r * 2 +
        ",0 " +
        arc +
        -r * 2 +
        ",0 ";

    this._setPath(layer, d);
  },
  _setPath: function (layer, path) {
    layer._path.setAttribute("d", path);
  },
  _bringToFront: function (layer) {
    DomUtil.toFront(layer._path); // 路径元素移动到DOM最前面
  },
  _bringToBack: function (layer) {
    DomUtil.toBack(layer._path); // 路径元素移动到DOM最后面
  },
});

if (Browser.vml) {
  SVG.include(vmlMixin); // 混入 VML 特定方法
}

export function svg() {
  return Browser, svg || Browser.vml ? new SVG(options) : null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

# 源码解析

# 兼容性处理

VML兼容 对于不支持SVG的旧版IE(如IE6 - 8),Leaflet提供了VML(Vector Markup Language)的兼容实现。通过Browser.vml判断是否需要使用VML渲染器。

  • create工厂函数根据浏览器支持选择创建SVG或VML元素
  • vmlMixin在VML模式下混入额外方法

# 容器初始化与销毁

  • _initContainer()初始化

    • 根容器设置
      • SVG根元素设置pointer-events:none;,避免遮挡地图其他交互
      • 子元素(如<path>)需单独开启事件,通过leaflet-interactive类
  • _destroyContainer()销毁

# 更新渲染器视图_update()

  • viewBox优化:通过调整viewBox的minX和minY实现地图平移,无需更新所有路径坐标。

# 路径生命周期管理

1._initPath(layer):初始化路径 2._addPath(layer):添加路径 3._removePath(layer):路径移除

# 路径更新与样式

  1. _updatePath(layer):路径更新

    • 调用_project()重新投影坐标
    • 调用_update()更新路径数据
  2. _updateStyle(layer):样式更新

    • 应用描边和填充样式
    • 支持虚线效果
  3. _updatePoly(layer, closed):多边形路径更新

    • pointesToPath()方法会将多边形的多个环转换为SVG路径的d属性字符串
    • 调用pointsToPath()生成路径数据
  4. _updateCircle(layer):圆形路径更新 使用两个半圆弧拼接成完整的圆

    • 计算圆形的SVG路径数据
    • 处理空圆形

# 图层排序

  • _bringToFront(layer):图层前置
  • _bringToBack(layer):图层后置

# 设计亮点

​1. ​性能优化​​

  • 通过 viewBox 平移代替修改所有路径坐标。

  • 批量更新路径而非逐帧渲染。 ​​ 2. 兼容性处理​​

  • 自动降级到 VML,支持旧版 IE。

  • 统一 API 屏蔽底层差异。 ​​ 3. 扩展性​​

  • 子类只需关注如何将几何数据转换为 SVG/VML 路径。

  • 通过混入模式(include)灵活扩展功能

# 总结

Leaflet 的 SVG 渲染器在保证跨浏览器兼容性的同时,高效渲染矢量图形,并提供了灵活的样式控制和层级管理。

编辑 (opens new window)
上次更新: 2025/05/12, 07:40:30
Render.getRenderer
SVG.Util

← Render.getRenderer SVG.Util→

最近更新
01
GeoJSON
05-08
02
Circle
04-15
03
CircleMarker
04-15
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式