Canvas 渲染后,拖动地图留下残影
为了提升性能打开了 preferCanvas,结果平移时老的圆形没擦掉,缩小时整片线网糊成一团。
现象
拖动过程中,前一帧的圆点残留在画布上,松手后逐渐消失。 缩放时线条变粗、交叉处出现深色斑块。 要素数量在 3000 以上时最明显,数量少时几乎看不出来。
原因
Leaflet 的 Canvas 渲染器只在视口范围内绘制,并留出一个小边距
padding(默认 0.1,即 10% 视口)。平移超过这个边距后,
新进入的区域没有预绘制,旧区域又来不及清除,就出现残影。
缩放过程中默认不重绘(updateWhenZooming: false),
画布被整体缩放,线条按比例放大后变粗,形成视觉上的“糊”。
排查过程
- 把
preferCanvas关掉,用 SVG 渲染对照,确认残影只在 Canvas 下出现。 - 减少要素到 500 个,看残影是否消失,判断是否与重绘耗时有关。
- 打印渲染器选项:
layer.getRenderer().options。
console.log(layer.getRenderer().options);
// { padding: 0.1, tolerance: 0, updateWhenZooming: false }
解决方案
按要素范围与移动幅度调大 padding,让渲染器提前绘制视口外的内容。
代价是每次重绘面积变大,需要与要素数量权衡:
var canvasRenderer = L.canvas({
padding: 0.5, // 预绘制 50% 视口边距
tolerance: 5, // 点击命中的像素容差
updateWhenZooming: true
});
var layer = L.geoJSON(data, {
renderer: canvasRenderer,
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
radius: 4,
weight: 1,
color: '#1c7ed6',
fillOpacity: 0.6
});
}
}).addTo(map);
要素很多、缩放时确实卡,可以保留 updateWhenZooming: false,
改为缩放结束后统一重绘,同时把线条宽度设成固定像素:
L.geoJSON(data, {
renderer: L.canvas({ padding: 0.3, updateWhenZooming: false }),
style: function () {
return { weight: 1, opacity: 0.8 }; // 用整数像素,缩放时不变形
}
}).addTo(map);
仍然有残影时,把矢量图层拆成两个渲染器:点层用一个,面层用另一个。 分开清屏,避免大面积填充反复覆盖:
var pointRenderer = L.canvas({ padding: 0.5 });
var polygonRenderer = L.canvas({ padding: 0.2 });
L.geoJSON(points, { renderer: pointRenderer }).addTo(map);
L.geoJSON(polygons, { renderer: polygonRenderer }).addTo(map);
验证
连续拖动地图 10 秒,松开后画布上没有旧图形残留。
缩放到 16 级,线宽保持 1 像素左右,不出现成片深色斑块。
用 performance 面板确认拖动时帧率仍在 40 fps 以上。
优先加 padding:残影的根因是预绘制范围不足。
padding 从 0.1 提到 0.5 通常就能解决,
比换渲染方案改动小得多。
经纬志(2026)。《Canvas 渲染后,拖动地图留下残影》。https://www.jingweizhi.com/cookbook/leaflet-canvas-ghost.html(访问日期 2026-11-03)。