概述
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;
}
源码解析
生命周期管理
-
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. 扩展性:统一接口支持多种几何类型(线、多边形、圆)