Canvas
# 概述
Canvas
渲染器是Leaflet的一个内置的高效矢量图形渲染器,用于在HTML5的Canvas
元素上绘制矢量图形。它继承自Renderer
类,提供了基本的渲染功能,如路径绘制、样式设置和事件处理。
# 源码分析
# 源码实现
Canvas
渲染器源码实现如下:
export var Canvas = Renderer.extend({
options: {
tolerance: 0, //控制路径简化精度,用于优化复杂路径的渲染性能,0表示不简化
},
getEvents: function () {
var events = Renderer.prototype.getEvents.call(this);
events.viewprereset = this._onViewPreReset; // 监听视图预重置事件
return events;
},
_onViewPreReset: function () {
this._postponeUpdatePaths = true;
},
onAdd: function () {
Renderer.prototype.onAdd.call(this); // 调用父类方法绑定容器
this._draw(); // 初始绘制
},
_initContainer: function () {
// 创建 Canvas 元素
var container = (this._container = document.createElement("canvas"));
// 绑定鼠标事件
DomEvent.on(container, "mousemove", this._onMouseMove, this);
DomEvent.on(
container,
"click dblclick mousedown mouseup contextmenu",
this._onClick,
this
);
DomEvent.on(container, "mouseout", this._handleMouseOut, this);
container["_leaflet_disable_events"] = true; // 禁用事件冒泡,避免事件穿透到地图容器
// 获取 Canvas 2D 上下文
this._ctx = container.getContext("2d");
},
_destroyContainer: function () {
Util.cancelAnimFrame(this._redrawRequest); // 取消待处理的重绘请求
delete this._ctx; // 清理上下文
DomUtil.remove(this._container); // 移除DOM元素
DomEvent.off(this._container); // 解绑事件
delete this._container; // 清理DOM元素引用
},
_updatePaths: function () {
if (this._postponeUpdatePaths) {
return;
}
var layer;
this._redrawBounds = null;
// 遍历图层,更新图层的像素坐标
for (var id in this._layers) {
layer = this._layers[id];
layer._update();
}
this._redraw(); //执行重绘
},
_update: function () {
if (this._map._animatingZoom && this._bounds) {
return;
}
Renderer.prototype._update.call(this);
var b = this._bounds,
container = this._container,
size = b.getSize(),
m = Browser.retina ? 2 : 1;
DomUtil.setPosition(container, b.min);
// Retina 优化
container.width = m * size.x;
container.height = m * size.y;
container.style.width = size.x + "px";
container.style.height = size.y + "px";
if (Browser.retina) {
// Retina 下缩放画布
this._ctx.scale(2, 2);
}
// 调整坐标系偏移
this._ctx.translate(-b.min.x, -b.min.y);
// 触发update事件
this.fire("update");
},
// 视图重置后恢复路径更新
_reset: function () {
Renderer.prototype._reset.call(this);
if (this._postponeUpdatePaths) {
this._postponeUpdatePaths = false;
this._updatePaths();
}
},
_initPath: function (layer) {
this._updateDashArray(layer);
this._layers[Util.stamp(layer)] = layer;
// 双向链表结构
var order = (layer._order = {
layer: layer,
prev: this._drawLast,
next: null,
});
if (this._drawLast) {
this._drawLast.next = order;
}
this._drawLast = order;
this._drawFirst = this._drawFirst || this._drawLast;
},
_addPath: function (layer) {
this._requestRedraw(layer);// 添加图层后触发重绘
},
_removePath: function (layer) {
var order = layer._order;
var next = order.next;
var prev = order.prev;
// 从链表中移除节点,更新头尾指针
if (next) {
next.prev = prev;
} else {
this._drawLast = prev;
}
if (prev) {
prev.next = next;
} else {
this._drawFirst = next;
}
delete layer._order;
delete this._layers[Util.stamp(layer)]; // 从图层集合中移除
this._requestRedraw(layer);
},
_updatePath: function (layer) {
this._extendRedrawBounds(layer);
layer._project();
layer._update();
this._requestRedraw(layer);
},
_updateStyle: function (layer) {
this._updateDashArray(layer);
this._requestRedraw(layer);
},
_updateDashArray: function (layer) {
if (typeof layer.options.dashArray === "string") {
var parts = layer.options.dashArray.split(/[, ]+/),
dashArray = [],
dashValue,
i;
for (i = 0; i < parts.length; i++) {
dashValue = Number(parts[i]);
if (isNaN(dashValue)) {
return;
}
dashArray.push(dashValue);
}
layer.options._dashArray = dashArray;
} else {
layer.options._dashArray = layer.options.dashArray;
}
},
_requestRedraw: function (layer) {
if (!this._map) {
return;
}
this._extendRedrawBounds(layer);
this._redrawRequest =
this._redrawRequest || Util.requestAnimFrame(this._redraw, this); // 合并重绘请求
},
_extendRedrawBounds: function (layer) {
// 计算需重绘的区域
if (layer._pxBounds) {
var padding = (layer.options.weight || 0) + 1;
this._redrawBounds = this._redrawBounds || new Bounds();
this._redrawBounds.extend(
layer._pxBounds.min.subtract([padding, padding])
);
this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
}
},
_redraw: function () {
this._redrawRequest = null;
if (this._redrawBounds) {
this._redrawBounds.min._floor();
this._redrawBounds.max._ceil();
}
this._clear(); //清空画布或局部区域
this._draw(); // 重新绘制所有可见图层
this._redrawBounds = null;
},
// 清空画布内容
_clear: function () {
var bounds = this._redrawBounds;
if (bounds) {
// 局部清除
var size = bounds.getSize();
this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
} else {
this._ctx.save();
this._ctx.setTransform(1, 0, 0, 1, 0, 0);
// 全屏清除
this._ctx.clearRect(0, 0, this._container.width, this._container.height);
this._ctx.restore();
}
},
_draw: function () {
var layer,
bounds = this._redrawBounds;
this._ctx.save();
if (bounds) {
var size = bounds.getSize();
this._ctx.beginPath();
this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
this._ctx.clip(); // 局部重绘时设置裁剪区域
}
this._drawing = true;
// 遍历链表
for (var order = this._drawFirst; order; order = order.next) {
layer = order.layer;
// 按顺序绘制图层
if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
layer._updatePath();
}
}
this._drawing = false;
this._ctx.restore();
},
// 绘制多边形或线段的路径
_updatePoly: function (layer, closed) {
if (!this._drawing) {
return;
}
var i,
j,
len2,
p,
parts = layer._parts,
len = parts.length,
ctx = this._ctx;
if (!len) {
return;
}
ctx.beginPath();
for (i = 0; i < len; i++) {
for (j = 0, len2 = parts[i].length; j < len2; j++) {
p = parts[i][j];
ctx[j ? "lineTo" : "moveTo"](p.x, p.y);
}
//是否闭合
if (closed) {
ctx.closePath();
}
}
this._fillStroke(ctx, layer); // 填充和描边
},
// 绘制圆形或椭圆
_updateCircle: function (layer) {
if (!this._drawing || layer._empty()) {
return;
}
var p = layer._point,
ctx = this._ctx,
r = Math.max(Math.round(layer._radius), 1),
s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
if (s !== 1) {
ctx.save();
ctx.scale(1, s);
}
ctx.beginPath();
ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false); // 绘制圆形路径
// 处理椭圆缩放
if (s !== 1) {
ctx.restore();
}
this._fillStroke(ctx, layer);
},
_fillStroke: function (ctx, layer) {
var options = layer.options;
if (options.fill) {
ctx.globalAlpha = options.fillOpacity;
ctx.fillStyle = options.fillColor || options.color;
ctx.fill(options.fillRule || "evenodd");
}
if (options.stroke && options.weight !== 0) {
// 虚线支持
if (ctx.setLineDash) {
ctx.setLineDash((layer.options && layer.options._dashArray) || []);
}
ctx.globalAlpha = options.opacity;
ctx.lineWidth = options.weight;
ctx.strokeStyle = options.color;
ctx.lineCap = options.lineCap;
ctx.lineJoin = options.lineJoin;
ctx.stroke();
}
},
_onClick: function (e) {
var point = this._map.mouseEventToLayerPoint(e),
layer,
clickedLayer;
for (var order = this._drawFirst; order; order = order.next) {
layer = order.layer;
//检测点击目标图层
if (layer.options.interactive && layer._containsPoint(point)) {
if (
!(e.type === "click" || e.type === "preclick") ||
!this._map._draggableMoved(layer)
) {
clickedLayer = layer;
}
}
}
this._fireEvent(clickedLayer ? [clickedLayer] : false, e); //触发事件
},
_onMouseMove: function (e) {
if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) {
return;
}
var point = this._map.mouseEventToLayerPoint(e);
this._handleMouseHover(e, point);
},
_handleMouseOut: function (e) {
var layer = this._hoveredLayer;
if (layer) {
DomUtil.removeClass(this._container, "leaflet-interactive");
this._fireEvent([layer], e, "mouseout");
this._hoveredLayer = null;
this._mouseHoverThrottled = false;
}
},
_handleMouseHover: function (e, point) {
if (this._mouseHoverThrottled) {
return;
}
var layer, candidateHoveredLayer;
for (var order = this._drawFirst; order; order = order.next) {
layer = order.layer;
if (layer.options.interactive && layer._containsPoint(point)) {
candidateHoveredLayer = layer;
}
}
if (candidateHoveredLayer !== this._hoveredLayer) {
this._handleMouseOut(e);
if (candidateHoveredLayer) {
DomUtil.addClass(this._container, "leaflet-interactive"); // change cursor
this._fireEvent([candidateHoveredLayer], e, "mouseover");
this._hoveredLayer = candidateHoveredLayer;
}
}
this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);
this._mouseHoverThrottled = true;
setTimeout(
Util.bind(function () {
this._mouseHoverThrottled = false;
}, this),
32
);
},
_fireEvent: function (layers, e, type) {
this._map._fireDOMEvent(e, type || e.type, layers);
},
_bringToFront: function (layer) {
var order = layer._order;
if (!order) {
return;
}
var next = order.next;
var prev = order.prev;
if (next) {
next.prev = prev;
} else {
return;
}
if (prev) {
prev.next = next;
} else if (next) {
this._drawFirst = next;
}
order.prev = this._drawLast;
this._drawLast.next = order;
order.next = null;
this._drawLast = order;
this._requestRedraw(layer);
},
_bringToBack: function (layer) {
var order = layer._order;
if (!order) {
return;
}
var next = order.next;
var prev = order.prev;
if (prev) {
prev.next = next;
} else {
return;
}
if (next) {
next.prev = prev;
} else if (prev) {
this._drawLast = prev;
}
order.prev = null;
order.next = this._drawFirst;
this._drawFirst.prev = order;
this._drawFirst = order;
this._requestRedraw(layer);
},
});
export function canvas(options) {
return Browser.canvas ? new Canvas(options) : null;
}
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# 源码解析
# 生命周期管理
getEvents()
:在地图重置前标记_postponeUpdatePaths
,延迟路径更新onAdd()
:将Canvas容器添加到地图后立即渲染_initContainer()
:通过DOM事件实现Canvas图层交互_destroyContainer()
:销毁时释放资源,防止内存泄漏
# 渲染更新机制
_update()
:通过scale
和translate
适配高清屏与地图偏移_requestRedraw()
与_extendRedrawBounds
:性能优化,使用requestAnimFrame
批量处理重绘,局部更新减少渲染开销
# 路径管理(双向链表)
_initPath
:通过链表结构管理图层渲染顺序,确保bringToFront
/bringToBack
高效执行addPath(layer)
和_removePath(layer)
:动态维护链表结构,支持图层增删
# 绘制核心逻辑
_draw()
:通过裁剪区域(clip
)限制绘制范围,提升性能_updatePoly(layer,closed)
和_updateCircle(layer)
:根据几何图形的类型,调用不同的Canvas API绘制路径_fillStroke(ctx, layer)
:填充和描边逻辑,支持虚线
# 交互处理
_onClick(e)
:通过遍历图层,检测点击目标图层_onMouseMove(e)
和_handleMouseHover(e, point)
:通过_containsPoint
判断鼠标位置,触发mouseover
和mouseout
事件
# 总结
Canvas渲染器的核心设计:
1. 性能优化:局部重绘、链表管理、requestAnimFrame
批量更新。
2. 交互实现:通过坐标转换与路径检测模拟 DOM 事件。
3. 渲染隔离:双向链表维护绘制顺序,Retina 适配。
4. 扩展性:统一接口支持多种几何类型(线、多边形、圆)